11import logging
2+ from uuid import UUID
23
3- from fastapi import APIRouter , Depends
4+ from fastapi import APIRouter , Depends , HTTPException
45
56from app .api .deps import AuthContextDep , SessionDep
67from app .api .permissions import Permission , require_permission
7- from app .models import LLMCallRequest , LLMCallResponse , Message
8+ from app .crud .jobs import JobCrud
9+ from app .crud .llm import get_llm_calls_by_job_id
10+ from app .models import (
11+ LLMCallRequest ,
12+ LLMCallResponse ,
13+ LLMJobImmediatePublic ,
14+ LLMJobPublic ,
15+ JobStatus ,
16+ )
17+ from app .models .llm .response import LLMResponse , Usage
818from app .services .llm .jobs import start_job
919from app .utils import APIResponse , validate_callback_url , load_description
1020
@@ -34,7 +44,7 @@ def llm_callback_notification(body: APIResponse[LLMCallResponse]):
3444@router .post (
3545 "/llm/call" ,
3646 description = load_description ("llm/llm_call.md" ),
37- response_model = APIResponse [Message ],
47+ response_model = APIResponse [LLMJobImmediatePublic ],
3848 callbacks = llm_callback_router .routes ,
3949 dependencies = [Depends (require_permission (Permission .REQUIRE_PROJECT ))],
4050)
@@ -43,22 +53,102 @@ def llm_call(
4353):
4454 """
4555 Endpoint to initiate an LLM call as a background job.
56+ Returns job information for polling.
4657 """
4758 project_id = _current_user .project_ .id
4859 organization_id = _current_user .organization_ .id
4960
5061 if request .callback_url :
5162 validate_callback_url (str (request .callback_url ))
5263
53- start_job (
64+ job_id = start_job (
5465 db = session ,
5566 request = request ,
5667 project_id = project_id ,
5768 organization_id = organization_id ,
5869 )
5970
60- return APIResponse .success_response (
61- data = Message (
62- message = f"Your response is being generated and will be delivered via callback."
63- ),
71+ # Fetch job details to return immediate response
72+ job_crud = JobCrud (session = session )
73+ job = job_crud .get (job_id = job_id , project_id = project_id )
74+
75+ if not job :
76+ raise HTTPException (status_code = 404 , detail = "Job not found" )
77+
78+ message = "Your response is being generated and will be delivered via callback."
79+ if not request .callback_url :
80+ message = "Your response is being generated"
81+
82+ job_response = LLMJobImmediatePublic (
83+ job_id = job .id ,
84+ status = job .status .value ,
85+ message = message ,
86+ job_inserted_at = job .created_at ,
87+ job_updated_at = job .updated_at ,
6488 )
89+
90+ return APIResponse .success_response (data = job_response )
91+
92+
93+ @router .get (
94+ "/llm/call/{job_id}" ,
95+ description = load_description ("llm/get_llm_call.md" ),
96+ response_model = APIResponse [LLMJobPublic ],
97+ dependencies = [Depends (require_permission (Permission .REQUIRE_PROJECT ))],
98+ )
99+ def get_llm_call_status (
100+ _current_user : AuthContextDep ,
101+ session : SessionDep ,
102+ job_id : UUID ,
103+ ) -> APIResponse [LLMJobPublic ]:
104+ """
105+ Poll for LLM call job status and results.
106+ Returns job information with nested LLM response when complete.
107+ """
108+
109+ project_id = _current_user .project_ .id
110+
111+ job_crud = JobCrud (session = session )
112+ job = job_crud .get (job_id = job_id , project_id = project_id )
113+
114+ if not job :
115+ raise HTTPException (status_code = 404 , detail = "Job not found" )
116+
117+ llm_call_response = None
118+ if job .status .value == JobStatus .SUCCESS :
119+ llm_calls = get_llm_calls_by_job_id (
120+ session = session , job_id = job_id , project_id = project_id
121+ )
122+
123+ if llm_calls :
124+ # Get the first LLM call from the list which will be the only call for the job id
125+ # since we initially won't be using this endpoint for llm chains
126+ llm_call = llm_calls [0 ]
127+
128+ llm_response = LLMResponse (
129+ provider_response_id = llm_call .provider_response_id or "" ,
130+ conversation_id = llm_call .conversation_id ,
131+ provider = llm_call .provider ,
132+ model = llm_call .model ,
133+ output = llm_call .content ,
134+ )
135+
136+ if not llm_call .usage :
137+ logger .warning (
138+ f"[get_llm_call] Missing usage data for llm_call job_id={ job_id } , project_id={ project_id } "
139+ )
140+
141+ llm_call_response = LLMCallResponse (
142+ response = llm_response ,
143+ usage = Usage (** llm_call .usage ),
144+ provider_raw_response = None ,
145+ )
146+
147+ job_response = LLMJobPublic (
148+ job_id = job .id ,
149+ status = job .status .value ,
150+ llm_response = llm_call_response ,
151+ error_message = job .error_message ,
152+ )
153+
154+ return APIResponse .success_response (data = job_response )
0 commit comments