@@ -59,6 +59,23 @@ def completion(
5959 ],
6060)
6161
62+ TASK_OBJECT_DETECTION = completion (
63+ '{"name": "object_detection", "result": {"objects": [{"label": "bus", "box": [0, 0, 10, 10]}]}}'
64+ )
65+ TASK_GUI_DETECTION = completion (
66+ '{"name": "gui_detection", "result": {"elements": [{"label": "button", "box": [1, 2, 3, 4]}]}}'
67+ )
68+ TASK_TRANSCRIBE = completion ('{"name": "speech_to_text", "result": {"text": "hello world"}}' )
69+ TASK_WEB_SEARCH = completion (
70+ '{"name": "web_search", "result": {"results": [{"title": "AI agents", "url": "https://example.com"}]}}'
71+ )
72+ TASK_SCRAPE = completion ('{"name": "scraper", "result": {"text": "Hacker News"}}' )
73+ TASK_TRANSLATE = completion ('{"name": "translate", "result": "Bonjour"}' )
74+ FORECAST_PRECONTEXT = completion (
75+ "Here is the forecast." , precontext = [{"name" : "forecast" , "result" : {"forecast" : [1 , 2 , 3 ]}}]
76+ )
77+ FORECAST_FALLBACK = completion ("I couldn't run the forecast tool; here's a manual estimate." )
78+
6279
6380def _chunk (delta : Dict [str , Any ], finish_reason = None ) -> Dict [str , Any ]:
6481 return {
@@ -83,12 +100,72 @@ def _chunk(delta: Dict[str, Any], finish_reason=None) -> Dict[str, Any]:
83100 _chunk ({}, finish_reason = "stop" ),
84101]
85102
103+ # Tool-call arguments split across chunks, as the wire actually streams them.
104+ STREAM_TOOL_CALL_CHUNKS : List [Dict [str , Any ]] = [
105+ _chunk (
106+ {
107+ "tool_calls" : [
108+ {
109+ "index" : 0 ,
110+ "id" : "call_1" ,
111+ "type" : "function" ,
112+ "function" : {"name" : "get_weather" , "arguments" : '{"ci' },
113+ }
114+ ]
115+ }
116+ ),
117+ _chunk ({"tool_calls" : [{"index" : 0 , "function" : {"arguments" : 'ty": "Pa' }}]}),
118+ _chunk ({"tool_calls" : [{"index" : 0 , "function" : {"arguments" : 'ris"}' }}]}, finish_reason = "tool_calls" ),
119+ ]
120+
121+ # A <precontext> block with invalid JSON inside — interfaze must swallow it, not crash.
122+ STREAM_MALFORMED_PRECONTEXT_CHUNKS : List [Dict [str , Any ]] = [
123+ _chunk ({"content" : "<precontext>[not valid json]</precontext>" }),
124+ _chunk ({"content" : "Answer anyway." }, finish_reason = "stop" ),
125+ ]
126+
127+ # A heartbeat/ping chunk with no choices at all, as some SSE proxies emit mid-stream.
128+ STREAM_CHUNK_NO_CHOICES : Dict [str , Any ] = {
129+ "id" : "req-test" ,
130+ "object" : "chat.completion.chunk" ,
131+ "created" : 1_700_000_000 ,
132+ "model" : "interfaze-beta" ,
133+ "choices" : [],
134+ }
135+ STREAM_ROLE_THEN_CONTENT_CHUNKS : List [Dict [str , Any ]] = [
136+ _chunk ({"role" : "assistant" , "content" : "" }),
137+ _chunk ({"content" : "Hi there." }, finish_reason = "stop" ),
138+ ]
139+
140+ # Two parallel tool calls in a single delta — one arrives complete, one still has no arguments.
141+ STREAM_PARALLEL_TOOL_CALL_CHUNKS : List [Dict [str , Any ]] = [
142+ _chunk (
143+ {
144+ "tool_calls" : [
145+ {
146+ "index" : 0 ,
147+ "id" : "call_1" ,
148+ "type" : "function" ,
149+ "function" : {"name" : "get_weather" , "arguments" : '{"city": "Paris"}' },
150+ },
151+ {"index" : 1 , "id" : "call_2" , "type" : "function" , "function" : {"name" : "get_time" }},
152+ ]
153+ },
154+ finish_reason = "tool_calls" ,
155+ ),
156+ ]
157+
86158
87159def mock_json (body : Dict [str , Any ]) -> respx .Route :
88160 """Route POST /chat/completions -> a JSON completion; returns the route (inspect .calls)."""
89161 return respx .post (CHAT_URL ).mock (return_value = httpx .Response (200 , json = body ))
90162
91163
164+ def mock_status (status : int , body : Dict [str , Any ]) -> respx .Route :
165+ """Route POST /chat/completions -> an error status with a realistic error body."""
166+ return respx .post (CHAT_URL ).mock (return_value = httpx .Response (status , json = body ))
167+
168+
92169def sse_bytes (chunks : List [Dict [str , Any ]]) -> bytes :
93170 return ("" .join (f"data: { json .dumps (c )} \n \n " for c in chunks ) + "data: [DONE]\n \n " ).encode ()
94171
@@ -101,6 +178,11 @@ def mock_sse(chunks: List[Dict[str, Any]]) -> respx.Route:
101178 )
102179
103180
181+ def error_body (message : str , type_ : str , code : str ) -> Dict [str , Any ]:
182+ """Shape of a real Interfaze error response body."""
183+ return {"error" : {"message" : message , "type" : type_ , "code" : code }}
184+
185+
104186def last_body (route : respx .Route ) -> Dict [str , Any ]:
105187 return json .loads (route .calls .last .request .content )
106188
0 commit comments