Skip to content

Commit fb0b274

Browse files
Tool calls partly working, though we're failing both to stream the tool call to the frontend and to resume after submitting tool output
1 parent c154822 commit fb0b274

File tree

3 files changed

+57
-14
lines changed

3 files changed

+57
-14
lines changed

routers/chat.py

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
from fastapi.responses import StreamingResponse, HTMLResponse
77
from openai import AsyncOpenAI
88
from openai.resources.beta.threads.runs.runs import AsyncAssistantStreamManager
9-
from openai.types.beta.assistant_stream_event import ThreadMessageCreated, ThreadMessageDelta, ThreadRunCompleted
9+
from openai.types.beta.assistant_stream_event import ThreadMessageCreated, ThreadMessageDelta, ThreadRunCompleted, ThreadRunRequiresAction
1010
from fastapi.responses import StreamingResponse
1111
from fastapi import APIRouter, Depends, Form, HTTPException
1212
from pydantic import BaseModel
13+
import json
1314

15+
# Import our get_weather method
16+
from utils.weather import get_weather
1417

1518
logger: logging.Logger = logging.getLogger("uvicorn.error")
1619
logger.setLevel(logging.DEBUG)
@@ -30,20 +33,25 @@ class ToolCallOutputs(BaseModel):
3033
runId: str
3134

3235
async def post_tool_outputs(client: AsyncOpenAI, data: dict, thread_id: str):
33-
36+
"""
37+
data is expected to be something like
38+
39+
{
40+
"tool_outputs": {"location": "City", "temperature": 70, "conditions": "Sunny"},
41+
"runId": "some-run-id",
42+
}
43+
"""
3444
try:
35-
# Parse the JSON body into the ToolCallOutputs model
36-
tool_call_outputs = ToolCallOutputs(**data)
37-
38-
# Submit tool outputs stream
39-
stream = await client.beta.threads.runs.submit_tool_outputs_stream(
40-
thread_id,
41-
tool_call_outputs.runId,
42-
{"tool_outputs": tool_call_outputs.tool_outputs}
45+
outputs_list = [data["tool_outputs"]]
46+
47+
stream_manager = client.beta.threads.runs.submit_tool_outputs_stream(
48+
thread_id=thread_id,
49+
run_id=data["runId"],
50+
tool_outputs=outputs_list,
4351
)
4452

45-
# Return the stream as a response
46-
return stream.to_readable_stream()
53+
return stream_manager
54+
4755
except Exception as e:
4856
logger.error(f"Error submitting tool outputs: {e}")
4957
raise HTTPException(status_code=500, detail=str(e))
@@ -120,6 +128,34 @@ async def event_generator():
120128
f"data: {event.data.delta.content[0].text.value}\n\n"
121129
)
122130

131+
if isinstance(event, ThreadRunRequiresAction):
132+
required_action = event.data.required_action
133+
if required_action and required_action.submit_tool_outputs:
134+
for tool_call in required_action.submit_tool_outputs.tool_calls:
135+
yield (
136+
f"event: toolCallCreated\n"
137+
f"data: {templates.get_template('components/assistant-step.html').render(
138+
step_type='toolCall', stream_name=f'toolDelta{step_counter}'
139+
).replace('\n', '')}\n\n"
140+
)
141+
142+
if tool_call.type == "function" and tool_call.function.name == "get_weather":
143+
try:
144+
args = json.loads(tool_call.function.arguments)
145+
location = args.get("location", "Unknown")
146+
except Exception as err:
147+
logger.error(f"Failed to parse function arguments: {err}")
148+
location = "Unknown"
149+
150+
weather_output = get_weather(location)
151+
logger.info(f"Weather output: {weather_output}")
152+
153+
data_for_tool = {
154+
"tool_outputs": weather_output,
155+
"runId": event.data.id,
156+
}
157+
await post_tool_outputs(client, data_for_tool, thread_id)
158+
123159
if isinstance(event, ThreadRunCompleted):
124160
yield "event: endStream\ndata: DONE\n\n"
125161

static/styles.css

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ pre {
162162
white-space: pre-wrap;
163163
}
164164

165+
.toolCall {
166+
white-space: pre-wrap;
167+
}
168+
169+
165170
.fileViewer {
166171
display: flex;
167172
flex-direction: column;
@@ -416,9 +421,11 @@ pre {
416421

417422
.userMessage,
418423
.assistantMessage,
419-
.codeMessage {
424+
.codeMessage,
425+
.toolCall {
420426
margin: 8px 0;
421427
padding: 8px 16px;
428+
422429
align-self: flex-start;
423430
border-radius: 15px;
424431
max-width: 80%;

templates/components/assistant-run.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
<div class="assistant-run" hx-swap="beforeend"
33
hx-ext="sse"
44
sse-connect="/assistants/{{ assistant_id }}/messages/{{ thread_id }}/receive"
5-
sse-swap="messageCreated"
5+
sse-swap="messageCreated,toolCallCreated"
66
sse-close="endStream">
77
</div>

0 commit comments

Comments
 (0)