inspect-ai 0.3.70__py3-none-any.whl → 0.3.72__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. inspect_ai/_cli/eval.py +14 -8
  2. inspect_ai/_display/core/display.py +2 -0
  3. inspect_ai/_display/core/footer.py +13 -3
  4. inspect_ai/_display/plain/display.py +6 -2
  5. inspect_ai/_display/rich/display.py +19 -6
  6. inspect_ai/_display/textual/app.py +6 -1
  7. inspect_ai/_display/textual/display.py +4 -0
  8. inspect_ai/_display/textual/widgets/transcript.py +10 -6
  9. inspect_ai/_eval/task/run.py +5 -8
  10. inspect_ai/_util/content.py +20 -1
  11. inspect_ai/_util/transcript.py +10 -4
  12. inspect_ai/_util/working.py +4 -0
  13. inspect_ai/_view/www/App.css +6 -0
  14. inspect_ai/_view/www/dist/assets/index.css +115 -87
  15. inspect_ai/_view/www/dist/assets/index.js +5324 -2276
  16. inspect_ai/_view/www/eslint.config.mjs +24 -1
  17. inspect_ai/_view/www/log-schema.json +283 -20
  18. inspect_ai/_view/www/package.json +8 -3
  19. inspect_ai/_view/www/src/App.tsx +2 -2
  20. inspect_ai/_view/www/src/components/AnsiDisplay.tsx +4 -3
  21. inspect_ai/_view/www/src/components/Card.tsx +9 -8
  22. inspect_ai/_view/www/src/components/DownloadButton.tsx +2 -1
  23. inspect_ai/_view/www/src/components/EmptyPanel.tsx +2 -2
  24. inspect_ai/_view/www/src/components/ErrorPanel.tsx +4 -3
  25. inspect_ai/_view/www/src/components/ExpandablePanel.tsx +13 -5
  26. inspect_ai/_view/www/src/components/FindBand.tsx +3 -3
  27. inspect_ai/_view/www/src/components/HumanBaselineView.tsx +3 -3
  28. inspect_ai/_view/www/src/components/LabeledValue.tsx +5 -4
  29. inspect_ai/_view/www/src/components/LargeModal.tsx +18 -13
  30. inspect_ai/_view/www/src/components/{LightboxCarousel.css → LightboxCarousel.module.css} +22 -18
  31. inspect_ai/_view/www/src/components/LightboxCarousel.tsx +36 -27
  32. inspect_ai/_view/www/src/components/MessageBand.tsx +2 -1
  33. inspect_ai/_view/www/src/components/NavPills.tsx +9 -8
  34. inspect_ai/_view/www/src/components/ProgressBar.tsx +2 -1
  35. inspect_ai/_view/www/src/components/TabSet.tsx +21 -15
  36. inspect_ai/_view/www/src/index.tsx +2 -2
  37. inspect_ai/_view/www/src/metadata/MetaDataGrid.tsx +11 -9
  38. inspect_ai/_view/www/src/metadata/MetaDataView.tsx +3 -2
  39. inspect_ai/_view/www/src/metadata/MetadataGrid.module.css +1 -0
  40. inspect_ai/_view/www/src/metadata/RenderedContent.tsx +16 -0
  41. inspect_ai/_view/www/src/plan/DatasetDetailView.tsx +3 -2
  42. inspect_ai/_view/www/src/plan/DetailStep.tsx +2 -1
  43. inspect_ai/_view/www/src/plan/PlanCard.tsx +2 -5
  44. inspect_ai/_view/www/src/plan/PlanDetailView.tsx +6 -9
  45. inspect_ai/_view/www/src/plan/ScorerDetailView.tsx +2 -1
  46. inspect_ai/_view/www/src/plan/SolverDetailView.tsx +3 -3
  47. inspect_ai/_view/www/src/samples/InlineSampleDisplay.tsx +2 -2
  48. inspect_ai/_view/www/src/samples/SampleDialog.tsx +3 -3
  49. inspect_ai/_view/www/src/samples/SampleDisplay.tsx +2 -2
  50. inspect_ai/_view/www/src/samples/SampleSummaryView.tsx +2 -2
  51. inspect_ai/_view/www/src/samples/SamplesTools.tsx +2 -1
  52. inspect_ai/_view/www/src/samples/chat/ChatMessage.tsx +3 -19
  53. inspect_ai/_view/www/src/samples/chat/ChatMessageRenderer.tsx +2 -1
  54. inspect_ai/_view/www/src/samples/chat/ChatMessageRow.tsx +2 -1
  55. inspect_ai/_view/www/src/samples/chat/ChatView.tsx +2 -1
  56. inspect_ai/_view/www/src/samples/chat/ChatViewVirtualList.tsx +22 -7
  57. inspect_ai/_view/www/src/samples/chat/MessageContent.tsx +35 -6
  58. inspect_ai/_view/www/src/samples/chat/MessageContents.tsx +2 -2
  59. inspect_ai/_view/www/src/samples/chat/messages.ts +15 -2
  60. inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.tsx +13 -4
  61. inspect_ai/_view/www/src/samples/chat/tools/ToolInput.module.css +2 -2
  62. inspect_ai/_view/www/src/samples/chat/tools/ToolInput.tsx +18 -19
  63. inspect_ai/_view/www/src/samples/chat/tools/ToolOutput.module.css +1 -1
  64. inspect_ai/_view/www/src/samples/chat/tools/ToolOutput.tsx +4 -3
  65. inspect_ai/_view/www/src/samples/chat/tools/ToolTitle.tsx +2 -2
  66. inspect_ai/_view/www/src/samples/error/FlatSampleErrorView.tsx +2 -3
  67. inspect_ai/_view/www/src/samples/error/SampleErrorView.tsx +3 -2
  68. inspect_ai/_view/www/src/samples/list/SampleFooter.tsx +2 -1
  69. inspect_ai/_view/www/src/samples/list/SampleHeader.tsx +2 -1
  70. inspect_ai/_view/www/src/samples/list/SampleList.tsx +57 -45
  71. inspect_ai/_view/www/src/samples/list/SampleRow.tsx +2 -1
  72. inspect_ai/_view/www/src/samples/list/SampleSeparator.tsx +2 -1
  73. inspect_ai/_view/www/src/samples/sample-tools/EpochFilter.tsx +2 -2
  74. inspect_ai/_view/www/src/samples/sample-tools/SelectScorer.tsx +4 -3
  75. inspect_ai/_view/www/src/samples/sample-tools/SortFilter.tsx +2 -5
  76. inspect_ai/_view/www/src/samples/sample-tools/sample-filter/SampleFilter.tsx +2 -2
  77. inspect_ai/_view/www/src/samples/scores/SampleScoreView.tsx +2 -1
  78. inspect_ai/_view/www/src/samples/scores/SampleScores.tsx +2 -2
  79. inspect_ai/_view/www/src/samples/transcript/ApprovalEventView.tsx +2 -1
  80. inspect_ai/_view/www/src/samples/transcript/ErrorEventView.tsx +2 -1
  81. inspect_ai/_view/www/src/samples/transcript/InfoEventView.tsx +2 -1
  82. inspect_ai/_view/www/src/samples/transcript/InputEventView.tsx +2 -1
  83. inspect_ai/_view/www/src/samples/transcript/LoggerEventView.module.css +4 -0
  84. inspect_ai/_view/www/src/samples/transcript/LoggerEventView.tsx +12 -2
  85. inspect_ai/_view/www/src/samples/transcript/ModelEventView.module.css +1 -1
  86. inspect_ai/_view/www/src/samples/transcript/ModelEventView.tsx +25 -28
  87. inspect_ai/_view/www/src/samples/transcript/SampleInitEventView.tsx +2 -1
  88. inspect_ai/_view/www/src/samples/transcript/SampleLimitEventView.tsx +5 -4
  89. inspect_ai/_view/www/src/samples/transcript/SampleTranscript.tsx +2 -2
  90. inspect_ai/_view/www/src/samples/transcript/SandboxEventView.tsx +8 -7
  91. inspect_ai/_view/www/src/samples/transcript/ScoreEventView.tsx +2 -2
  92. inspect_ai/_view/www/src/samples/transcript/StepEventView.tsx +3 -3
  93. inspect_ai/_view/www/src/samples/transcript/SubtaskEventView.tsx +18 -14
  94. inspect_ai/_view/www/src/samples/transcript/ToolEventView.tsx +5 -5
  95. inspect_ai/_view/www/src/samples/transcript/TranscriptView.tsx +34 -15
  96. inspect_ai/_view/www/src/samples/transcript/event/EventNav.tsx +2 -1
  97. inspect_ai/_view/www/src/samples/transcript/event/EventNavs.tsx +2 -1
  98. inspect_ai/_view/www/src/samples/transcript/event/EventRow.tsx +3 -2
  99. inspect_ai/_view/www/src/samples/transcript/event/EventSection.tsx +2 -2
  100. inspect_ai/_view/www/src/samples/transcript/event/EventTimingPanel.module.css +28 -0
  101. inspect_ai/_view/www/src/samples/transcript/event/EventTimingPanel.tsx +115 -0
  102. inspect_ai/_view/www/src/samples/transcript/event/utils.ts +29 -0
  103. inspect_ai/_view/www/src/samples/transcript/state/StateDiffView.tsx +2 -1
  104. inspect_ai/_view/www/src/samples/transcript/state/StateEventRenderers.tsx +3 -3
  105. inspect_ai/_view/www/src/samples/transcript/state/StateEventView.tsx +11 -8
  106. inspect_ai/_view/www/src/types/log.d.ts +129 -34
  107. inspect_ai/_view/www/src/usage/ModelTokenTable.tsx +6 -10
  108. inspect_ai/_view/www/src/usage/ModelUsagePanel.module.css +4 -0
  109. inspect_ai/_view/www/src/usage/ModelUsagePanel.tsx +32 -9
  110. inspect_ai/_view/www/src/usage/TokenTable.tsx +4 -6
  111. inspect_ai/_view/www/src/usage/UsageCard.tsx +2 -1
  112. inspect_ai/_view/www/src/utils/format.ts +1 -1
  113. inspect_ai/_view/www/src/utils/json.ts +24 -0
  114. inspect_ai/_view/www/src/workspace/WorkSpace.tsx +6 -5
  115. inspect_ai/_view/www/src/workspace/WorkSpaceView.tsx +9 -2
  116. inspect_ai/_view/www/src/workspace/error/TaskErrorPanel.tsx +2 -1
  117. inspect_ai/_view/www/src/workspace/navbar/Navbar.tsx +2 -1
  118. inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.tsx +3 -3
  119. inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.tsx +4 -3
  120. inspect_ai/_view/www/src/workspace/navbar/SecondaryBar.tsx +5 -4
  121. inspect_ai/_view/www/src/workspace/navbar/StatusPanel.tsx +5 -8
  122. inspect_ai/_view/www/src/workspace/sidebar/EvalStatus.tsx +5 -4
  123. inspect_ai/_view/www/src/workspace/sidebar/LogDirectoryTitleView.tsx +2 -1
  124. inspect_ai/_view/www/src/workspace/sidebar/Sidebar.tsx +2 -1
  125. inspect_ai/_view/www/src/workspace/sidebar/SidebarLogEntry.tsx +2 -2
  126. inspect_ai/_view/www/src/workspace/sidebar/SidebarScoreView.tsx +2 -1
  127. inspect_ai/_view/www/src/workspace/sidebar/SidebarScoresView.tsx +2 -2
  128. inspect_ai/_view/www/src/workspace/tabs/InfoTab.tsx +2 -2
  129. inspect_ai/_view/www/src/workspace/tabs/JsonTab.tsx +2 -5
  130. inspect_ai/_view/www/src/workspace/tabs/SamplesTab.tsx +12 -11
  131. inspect_ai/_view/www/yarn.lock +241 -5
  132. inspect_ai/log/_condense.py +3 -0
  133. inspect_ai/log/_recorders/eval.py +6 -1
  134. inspect_ai/log/_transcript.py +58 -1
  135. inspect_ai/model/__init__.py +2 -0
  136. inspect_ai/model/_call_tools.py +7 -0
  137. inspect_ai/model/_chat_message.py +22 -7
  138. inspect_ai/model/_conversation.py +10 -8
  139. inspect_ai/model/_generate_config.py +25 -4
  140. inspect_ai/model/_model.py +133 -57
  141. inspect_ai/model/_model_output.py +3 -0
  142. inspect_ai/model/_openai.py +106 -40
  143. inspect_ai/model/_providers/anthropic.py +281 -153
  144. inspect_ai/model/_providers/google.py +27 -8
  145. inspect_ai/model/_providers/groq.py +9 -4
  146. inspect_ai/model/_providers/openai.py +57 -4
  147. inspect_ai/model/_providers/openai_o1.py +10 -0
  148. inspect_ai/model/_providers/providers.py +1 -1
  149. inspect_ai/model/_reasoning.py +15 -2
  150. inspect_ai/scorer/_model.py +23 -19
  151. inspect_ai/solver/_human_agent/agent.py +14 -10
  152. inspect_ai/solver/_human_agent/commands/__init__.py +7 -3
  153. inspect_ai/solver/_human_agent/commands/submit.py +76 -30
  154. inspect_ai/tool/__init__.py +2 -0
  155. inspect_ai/tool/_tool.py +3 -1
  156. inspect_ai/tool/_tools/_computer/_common.py +117 -58
  157. inspect_ai/tool/_tools/_computer/_computer.py +80 -57
  158. inspect_ai/tool/_tools/_computer/_resources/image_home_dir/.config/Code/User/settings.json +7 -1
  159. inspect_ai/tool/_tools/_computer/_resources/image_home_dir/.config/xfce4/xfconf/xfce-perchannel-xml/xfwm4.xml +91 -0
  160. inspect_ai/tool/_tools/_computer/_resources/tool/.pylintrc +8 -0
  161. inspect_ai/tool/_tools/_computer/_resources/tool/.vscode/settings.json +12 -0
  162. inspect_ai/tool/_tools/_computer/_resources/tool/_args.py +78 -0
  163. inspect_ai/tool/_tools/_computer/_resources/tool/_constants.py +20 -0
  164. inspect_ai/tool/_tools/_computer/_resources/tool/_run.py +1 -1
  165. inspect_ai/tool/_tools/_computer/_resources/tool/_x11_client.py +175 -113
  166. inspect_ai/tool/_tools/_computer/_resources/tool/computer_tool.py +76 -20
  167. inspect_ai/tool/_tools/_computer/_resources/tool/pyproject.toml +65 -0
  168. inspect_ai/tool/_tools/_computer/test_args.py +151 -0
  169. inspect_ai/tool/_tools/_web_browser/_resources/.pylintrc +8 -0
  170. inspect_ai/tool/_tools/_web_browser/_resources/.vscode/launch.json +24 -0
  171. inspect_ai/tool/_tools/_web_browser/_resources/.vscode/settings.json +25 -0
  172. inspect_ai/tool/_tools/_web_browser/_resources/Dockerfile +5 -6
  173. inspect_ai/tool/_tools/_web_browser/_resources/README.md +10 -11
  174. inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree.py +71 -0
  175. inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree_node.py +323 -0
  176. inspect_ai/tool/_tools/_web_browser/_resources/cdp/__init__.py +5 -0
  177. inspect_ai/tool/_tools/_web_browser/_resources/cdp/a11y.py +279 -0
  178. inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom.py +9 -0
  179. inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom_snapshot.py +293 -0
  180. inspect_ai/tool/_tools/_web_browser/_resources/cdp/page.py +94 -0
  181. inspect_ai/tool/_tools/_web_browser/_resources/constants.py +2 -0
  182. inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.svg +2 -0
  183. inspect_ai/tool/_tools/_web_browser/_resources/playwright_browser.py +50 -0
  184. inspect_ai/tool/_tools/_web_browser/_resources/playwright_crawler.py +31 -359
  185. inspect_ai/tool/_tools/_web_browser/_resources/playwright_page_crawler.py +280 -0
  186. inspect_ai/tool/_tools/_web_browser/_resources/pyproject.toml +65 -0
  187. inspect_ai/tool/_tools/_web_browser/_resources/rectangle.py +64 -0
  188. inspect_ai/tool/_tools/_web_browser/_resources/rpc_client_helpers.py +146 -0
  189. inspect_ai/tool/_tools/_web_browser/_resources/scale_factor.py +64 -0
  190. inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_tree_node.py +180 -0
  191. inspect_ai/tool/_tools/_web_browser/_resources/test_playwright_crawler.py +15 -9
  192. inspect_ai/tool/_tools/_web_browser/_resources/test_rectangle.py +15 -0
  193. inspect_ai/tool/_tools/_web_browser/_resources/test_web_client.py +44 -0
  194. inspect_ai/tool/_tools/_web_browser/_resources/web_browser_rpc_types.py +39 -0
  195. inspect_ai/tool/_tools/_web_browser/_resources/web_client.py +198 -48
  196. inspect_ai/tool/_tools/_web_browser/_resources/web_client_new_session.py +26 -25
  197. inspect_ai/tool/_tools/_web_browser/_resources/web_server.py +178 -39
  198. inspect_ai/tool/_tools/_web_browser/_web_browser.py +38 -19
  199. inspect_ai/util/__init__.py +2 -1
  200. inspect_ai/util/_display.py +12 -0
  201. inspect_ai/util/_sandbox/events.py +55 -21
  202. inspect_ai/util/_sandbox/self_check.py +131 -43
  203. inspect_ai/util/_subtask.py +11 -0
  204. {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.72.dist-info}/METADATA +1 -1
  205. {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.72.dist-info}/RECORD +209 -186
  206. {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.72.dist-info}/WHEEL +1 -1
  207. inspect_ai/_view/www/src/components/VirtualList.module.css +0 -19
  208. inspect_ai/_view/www/src/components/VirtualList.tsx +0 -292
  209. inspect_ai/tool/_tools/_computer/_computer_split.py +0 -198
  210. inspect_ai/tool/_tools/_web_browser/_resources/accessibility_node.py +0 -312
  211. inspect_ai/tool/_tools/_web_browser/_resources/dm_env_servicer.py +0 -275
  212. inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.png +0 -0
  213. inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_node.py +0 -176
  214. inspect_ai/tool/_tools/_web_browser/_resources/test_dm_env_servicer.py +0 -135
  215. inspect_ai/tool/_tools/_web_browser/_resources/test_web_environment.py +0 -71
  216. inspect_ai/tool/_tools/_web_browser/_resources/web_environment.py +0 -184
  217. {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.72.dist-info}/LICENSE +0 -0
  218. {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.72.dist-info}/entry_points.txt +0 -0
  219. {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.72.dist-info}/top_level.txt +0 -0
@@ -27,11 +27,18 @@ from openai.types.chat.chat_completion_message_tool_call import Function
27
27
  from openai.types.completion_usage import CompletionUsage
28
28
  from openai.types.shared_params.function_definition import FunctionDefinition
29
29
 
30
- from inspect_ai._util.content import Content, ContentAudio, ContentImage, ContentText
30
+ from inspect_ai._util.content import (
31
+ Content,
32
+ ContentAudio,
33
+ ContentImage,
34
+ ContentReasoning,
35
+ ContentText,
36
+ )
31
37
  from inspect_ai._util.images import file_as_data_uri
32
38
  from inspect_ai._util.url import is_http_url
33
39
  from inspect_ai.model._call_tools import parse_tool_call
34
40
  from inspect_ai.model._model_output import ChatCompletionChoice, Logprobs
41
+ from inspect_ai.model._reasoning import parse_content_with_reasoning
35
42
  from inspect_ai.tool import ToolCall, ToolChoice, ToolFunction, ToolInfo
36
43
 
37
44
  from ._chat_message import (
@@ -148,14 +155,14 @@ async def openai_chat_message(
148
155
  if message.tool_calls:
149
156
  return ChatCompletionAssistantMessageParam(
150
157
  role=message.role,
151
- content=message.text,
158
+ content=openai_assistant_content(message),
152
159
  tool_calls=[
153
160
  openai_chat_tool_call_param(call) for call in message.tool_calls
154
161
  ],
155
162
  )
156
163
  else:
157
164
  return ChatCompletionAssistantMessageParam(
158
- role=message.role, content=message.text
165
+ role=message.role, content=openai_assistant_content(message)
159
166
  )
160
167
  elif message.role == "tool":
161
168
  return ChatCompletionToolMessageParam(
@@ -175,16 +182,29 @@ async def openai_chat_messages(
175
182
  return [await openai_chat_message(message, model) for message in messages]
176
183
 
177
184
 
185
+ def openai_assistant_content(message: ChatMessageAssistant) -> str:
186
+ if isinstance(message.content, str):
187
+ content = message.content
188
+ else:
189
+ content = ""
190
+ for c in message.content:
191
+ if c.type == "reasoning":
192
+ attribs = ""
193
+ if c.signature is not None:
194
+ attribs = f'{attribs} signature="{c.signature}"'
195
+ if c.redacted:
196
+ attribs = f'{attribs} redacted="true"'
197
+ content = f"{content}\n<think{attribs}>\n{c.reasoning}\n</think>\n"
198
+ elif c.type == "text":
199
+ content = f"{content}\n{c.text}"
200
+ return content
201
+
202
+
178
203
  def openai_chat_choices(choices: list[ChatCompletionChoice]) -> list[Choice]:
179
204
  oai_choices: list[Choice] = []
180
205
 
181
206
  for index, choice in enumerate(choices):
182
- if isinstance(choice.message.content, str):
183
- content = choice.message.content
184
- else:
185
- content = "\n".join(
186
- [c.text for c in choice.message.content if c.type == "text"]
187
- )
207
+ content = openai_assistant_content(choice.message)
188
208
  if choice.message.tool_calls:
189
209
  tool_calls = [openai_chat_tool_call(tc) for tc in choice.message.tool_calls]
190
210
  else:
@@ -274,35 +294,47 @@ def chat_messages_from_openai(
274
294
  chat_messages: list[ChatMessage] = []
275
295
 
276
296
  for message in messages:
297
+ content: str | list[Content] = []
277
298
  if message["role"] == "system" or message["role"] == "developer":
278
299
  sys_content = message["content"]
279
300
  if isinstance(sys_content, str):
280
301
  chat_messages.append(ChatMessageSystem(content=sys_content))
281
302
  else:
282
- chat_messages.append(
283
- ChatMessageSystem(
284
- content=[content_from_openai(c) for c in sys_content]
285
- )
286
- )
303
+ content = []
304
+ for sc in sys_content:
305
+ content.extend(content_from_openai(sc))
306
+ chat_messages.append(ChatMessageSystem(content=content))
287
307
  elif message["role"] == "user":
288
308
  user_content = message["content"]
289
309
  if isinstance(user_content, str):
290
310
  chat_messages.append(ChatMessageUser(content=user_content))
291
311
  else:
292
- chat_messages.append(
293
- ChatMessageUser(
294
- content=[content_from_openai(c) for c in user_content]
295
- )
296
- )
312
+ content = []
313
+ for uc in user_content:
314
+ content.extend(content_from_openai(uc))
315
+ chat_messages.append(ChatMessageUser(content=content))
297
316
  elif message["role"] == "assistant":
298
317
  # resolve content
299
- asst_content = message["content"]
318
+ asst_content = message.get("content", None)
300
319
  if isinstance(asst_content, str):
301
- content: str | list[Content] = asst_content
320
+ result = parse_content_with_reasoning(asst_content)
321
+ if result is not None:
322
+ content = [
323
+ ContentReasoning(
324
+ reasoning=result.reasoning,
325
+ signature=result.signature,
326
+ redacted=result.redacted,
327
+ ),
328
+ ContentText(text=result.content),
329
+ ]
330
+ else:
331
+ content = asst_content
302
332
  elif asst_content is None:
303
333
  content = message.get("refusal", None) or ""
304
334
  else:
305
- content = [content_from_openai(c) for c in asst_content]
335
+ content = []
336
+ for ac in asst_content:
337
+ content.extend(content_from_openai(ac, parse_reasoning=True))
306
338
 
307
339
  # resolve reasoning (OpenAI doesn't suport this however OpenAI-compatible
308
340
  # interfaces e.g. DeepSeek do include this field so we pluck it out)
@@ -310,22 +342,25 @@ def chat_messages_from_openai(
310
342
  "reasoning", None
311
343
  )
312
344
  if reasoning is not None:
313
- reasoning = str(reasoning)
345
+ if isinstance(content, str):
346
+ content = [ContentText(text=content)]
347
+ else:
348
+ content.insert(0, ContentReasoning(reasoning=str(reasoning)))
314
349
 
315
350
  # return message
316
351
  if "tool_calls" in message:
317
352
  tool_calls: list[ToolCall] = []
318
- for tc in message["tool_calls"]:
319
- tool_calls.append(tool_call_from_openai(tc))
320
- tool_names[tc["id"]] = tc["function"]["name"]
353
+ for call in message["tool_calls"]:
354
+ tool_calls.append(tool_call_from_openai(call))
355
+ tool_names[call["id"]] = call["function"]["name"]
321
356
 
322
357
  else:
323
358
  tool_calls = []
359
+
324
360
  chat_messages.append(
325
361
  ChatMessageAssistant(
326
362
  content=content,
327
363
  tool_calls=tool_calls or None,
328
- reasoning=reasoning,
329
364
  )
330
365
  )
331
366
  elif message["role"] == "tool":
@@ -333,7 +368,9 @@ def chat_messages_from_openai(
333
368
  if isinstance(tool_content, str):
334
369
  content = tool_content
335
370
  else:
336
- content = [content_from_openai(c) for c in tool_content]
371
+ content = []
372
+ for tc in tool_content:
373
+ content.extend(content_from_openai(tc))
337
374
  chat_messages.append(
338
375
  ChatMessageTool(
339
376
  content=content,
@@ -357,20 +394,40 @@ def tool_call_from_openai(tool_call: ChatCompletionMessageToolCallParam) -> Tool
357
394
 
358
395
  def content_from_openai(
359
396
  content: ChatCompletionContentPartParam | ChatCompletionContentPartRefusalParam,
360
- ) -> Content:
397
+ parse_reasoning: bool = False,
398
+ ) -> list[Content]:
361
399
  if content["type"] == "text":
362
- return ContentText(text=content["text"])
400
+ text = content["text"]
401
+ if parse_reasoning:
402
+ result = parse_content_with_reasoning(text)
403
+ if result:
404
+ return [
405
+ ContentReasoning(
406
+ reasoning=result.reasoning,
407
+ signature=result.signature,
408
+ redacted=result.redacted,
409
+ ),
410
+ ContentText(text=result.content),
411
+ ]
412
+ else:
413
+ return [ContentText(text=text)]
414
+ else:
415
+ return [ContentText(text=text)]
363
416
  elif content["type"] == "image_url":
364
- return ContentImage(
365
- image=content["image_url"]["url"], detail=content["image_url"]["detail"]
366
- )
417
+ return [
418
+ ContentImage(
419
+ image=content["image_url"]["url"], detail=content["image_url"]["detail"]
420
+ )
421
+ ]
367
422
  elif content["type"] == "input_audio":
368
- return ContentAudio(
369
- audio=content["input_audio"]["data"],
370
- format=content["input_audio"]["format"],
371
- )
423
+ return [
424
+ ContentAudio(
425
+ audio=content["input_audio"]["data"],
426
+ format=content["input_audio"]["format"],
427
+ )
428
+ ]
372
429
  elif content["type"] == "refusal":
373
- return ContentText(text=content["refusal"])
430
+ return [ContentText(text=content["refusal"])]
374
431
 
375
432
 
376
433
  def chat_message_assistant_from_openai(
@@ -380,11 +437,20 @@ def chat_message_assistant_from_openai(
380
437
  reasoning = getattr(message, "reasoning_content", None) or getattr(
381
438
  message, "reasoning", None
382
439
  )
440
+
441
+ msg_content = refusal or message.content or ""
442
+ if reasoning is not None:
443
+ content: str | list[Content] = [
444
+ ContentReasoning(reasoning=str(reasoning)),
445
+ ContentText(text=msg_content),
446
+ ]
447
+ else:
448
+ content = msg_content
449
+
383
450
  return ChatMessageAssistant(
384
- content=refusal or message.content or "",
451
+ content=content,
385
452
  source="generate",
386
453
  tool_calls=chat_tool_calls_from_openai(message, tools),
387
- reasoning=reasoning,
388
454
  )
389
455
 
390
456