mirascope 2.0.0a5__py3-none-any.whl → 2.0.1__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 (349) hide show
  1. mirascope/__init__.py +10 -1
  2. mirascope/_stubs.py +363 -0
  3. mirascope/api/__init__.py +8 -0
  4. mirascope/api/_generated/__init__.py +285 -2
  5. mirascope/api/_generated/annotations/__init__.py +33 -0
  6. mirascope/api/_generated/annotations/client.py +506 -0
  7. mirascope/api/_generated/annotations/raw_client.py +1414 -0
  8. mirascope/api/_generated/annotations/types/__init__.py +31 -0
  9. mirascope/api/_generated/annotations/types/annotations_create_request_label.py +5 -0
  10. mirascope/api/_generated/annotations/types/annotations_create_response.py +48 -0
  11. mirascope/api/_generated/annotations/types/annotations_create_response_label.py +5 -0
  12. mirascope/api/_generated/annotations/types/annotations_get_response.py +48 -0
  13. mirascope/api/_generated/annotations/types/annotations_get_response_label.py +5 -0
  14. mirascope/api/_generated/annotations/types/annotations_list_request_label.py +5 -0
  15. mirascope/api/_generated/annotations/types/annotations_list_response.py +21 -0
  16. mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item.py +50 -0
  17. mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item_label.py +5 -0
  18. mirascope/api/_generated/annotations/types/annotations_update_request_label.py +5 -0
  19. mirascope/api/_generated/annotations/types/annotations_update_response.py +48 -0
  20. mirascope/api/_generated/annotations/types/annotations_update_response_label.py +5 -0
  21. mirascope/api/_generated/api_keys/__init__.py +12 -2
  22. mirascope/api/_generated/api_keys/client.py +77 -0
  23. mirascope/api/_generated/api_keys/raw_client.py +422 -39
  24. mirascope/api/_generated/api_keys/types/__init__.py +7 -1
  25. mirascope/api/_generated/api_keys/types/api_keys_create_response.py +4 -12
  26. mirascope/api/_generated/api_keys/types/api_keys_get_response.py +4 -12
  27. mirascope/api/_generated/api_keys/types/api_keys_list_all_for_org_response_item.py +40 -0
  28. mirascope/api/_generated/api_keys/types/api_keys_list_response_item.py +4 -12
  29. mirascope/api/_generated/client.py +42 -0
  30. mirascope/api/_generated/core/client_wrapper.py +2 -14
  31. mirascope/api/_generated/core/datetime_utils.py +1 -3
  32. mirascope/api/_generated/core/file.py +2 -5
  33. mirascope/api/_generated/core/http_client.py +36 -112
  34. mirascope/api/_generated/core/jsonable_encoder.py +1 -3
  35. mirascope/api/_generated/core/pydantic_utilities.py +19 -74
  36. mirascope/api/_generated/core/query_encoder.py +1 -3
  37. mirascope/api/_generated/core/serialization.py +4 -10
  38. mirascope/api/_generated/docs/client.py +2 -6
  39. mirascope/api/_generated/docs/raw_client.py +51 -5
  40. mirascope/api/_generated/environment.py +3 -3
  41. mirascope/api/_generated/environments/__init__.py +6 -0
  42. mirascope/api/_generated/environments/client.py +117 -0
  43. mirascope/api/_generated/environments/raw_client.py +530 -51
  44. mirascope/api/_generated/environments/types/__init__.py +10 -0
  45. mirascope/api/_generated/environments/types/environments_create_response.py +1 -3
  46. mirascope/api/_generated/environments/types/environments_get_analytics_response.py +60 -0
  47. mirascope/api/_generated/environments/types/environments_get_analytics_response_top_functions_item.py +24 -0
  48. mirascope/api/_generated/environments/types/environments_get_analytics_response_top_models_item.py +22 -0
  49. mirascope/api/_generated/environments/types/environments_get_response.py +1 -3
  50. mirascope/api/_generated/environments/types/environments_list_response_item.py +1 -3
  51. mirascope/api/_generated/environments/types/environments_update_response.py +1 -3
  52. mirascope/api/_generated/errors/__init__.py +8 -0
  53. mirascope/api/_generated/errors/bad_request_error.py +1 -2
  54. mirascope/api/_generated/errors/conflict_error.py +1 -2
  55. mirascope/api/_generated/errors/forbidden_error.py +1 -5
  56. mirascope/api/_generated/errors/internal_server_error.py +1 -6
  57. mirascope/api/_generated/errors/not_found_error.py +1 -5
  58. mirascope/api/_generated/errors/payment_required_error.py +15 -0
  59. mirascope/api/_generated/errors/service_unavailable_error.py +14 -0
  60. mirascope/api/_generated/errors/too_many_requests_error.py +15 -0
  61. mirascope/api/_generated/errors/unauthorized_error.py +11 -0
  62. mirascope/api/_generated/functions/__init__.py +39 -0
  63. mirascope/api/_generated/functions/client.py +647 -0
  64. mirascope/api/_generated/functions/raw_client.py +1890 -0
  65. mirascope/api/_generated/functions/types/__init__.py +53 -0
  66. mirascope/api/_generated/functions/types/functions_create_request_dependencies_value.py +20 -0
  67. mirascope/api/_generated/functions/types/functions_create_response.py +37 -0
  68. mirascope/api/_generated/functions/types/functions_create_response_dependencies_value.py +20 -0
  69. mirascope/api/_generated/functions/types/functions_find_by_hash_response.py +39 -0
  70. mirascope/api/_generated/functions/types/functions_find_by_hash_response_dependencies_value.py +20 -0
  71. mirascope/api/_generated/functions/types/functions_get_by_env_response.py +53 -0
  72. mirascope/api/_generated/functions/types/functions_get_by_env_response_dependencies_value.py +22 -0
  73. mirascope/api/_generated/functions/types/functions_get_response.py +37 -0
  74. mirascope/api/_generated/functions/types/functions_get_response_dependencies_value.py +20 -0
  75. mirascope/api/_generated/functions/types/functions_list_by_env_response.py +25 -0
  76. mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item.py +56 -0
  77. mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item_dependencies_value.py +22 -0
  78. mirascope/api/_generated/functions/types/functions_list_response.py +21 -0
  79. mirascope/api/_generated/functions/types/functions_list_response_functions_item.py +41 -0
  80. mirascope/api/_generated/functions/types/functions_list_response_functions_item_dependencies_value.py +20 -0
  81. mirascope/api/_generated/health/client.py +2 -6
  82. mirascope/api/_generated/health/raw_client.py +51 -5
  83. mirascope/api/_generated/health/types/health_check_response.py +1 -3
  84. mirascope/api/_generated/organization_invitations/__init__.py +33 -0
  85. mirascope/api/_generated/organization_invitations/client.py +546 -0
  86. mirascope/api/_generated/organization_invitations/raw_client.py +1519 -0
  87. mirascope/api/_generated/organization_invitations/types/__init__.py +53 -0
  88. mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response.py +34 -0
  89. mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response_role.py +7 -0
  90. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_request_role.py +7 -0
  91. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response.py +48 -0
  92. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_role.py +7 -0
  93. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_status.py +7 -0
  94. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response.py +48 -0
  95. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_role.py +7 -0
  96. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_status.py +7 -0
  97. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item.py +48 -0
  98. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_role.py +7 -0
  99. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_status.py +7 -0
  100. mirascope/api/_generated/organization_memberships/__init__.py +19 -0
  101. mirascope/api/_generated/organization_memberships/client.py +302 -0
  102. mirascope/api/_generated/organization_memberships/raw_client.py +736 -0
  103. mirascope/api/_generated/organization_memberships/types/__init__.py +27 -0
  104. mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item.py +33 -0
  105. mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item_role.py +7 -0
  106. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_request_role.py +7 -0
  107. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response.py +31 -0
  108. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response_role.py +7 -0
  109. mirascope/api/_generated/organizations/__init__.py +26 -0
  110. mirascope/api/_generated/organizations/client.py +465 -0
  111. mirascope/api/_generated/organizations/raw_client.py +1799 -108
  112. mirascope/api/_generated/organizations/types/__init__.py +48 -0
  113. mirascope/api/_generated/organizations/types/organizations_create_payment_intent_response.py +24 -0
  114. mirascope/api/_generated/organizations/types/organizations_create_response.py +4 -3
  115. mirascope/api/_generated/organizations/types/organizations_create_response_role.py +1 -3
  116. mirascope/api/_generated/organizations/types/organizations_get_response.py +4 -3
  117. mirascope/api/_generated/organizations/types/organizations_get_response_role.py +1 -3
  118. mirascope/api/_generated/organizations/types/organizations_list_response_item.py +4 -3
  119. mirascope/api/_generated/organizations/types/organizations_list_response_item_role.py +1 -3
  120. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_request_target_plan.py +7 -0
  121. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response.py +47 -0
  122. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item.py +33 -0
  123. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item_resource.py +7 -0
  124. mirascope/api/_generated/organizations/types/organizations_router_balance_response.py +24 -0
  125. mirascope/api/_generated/organizations/types/organizations_subscription_response.py +53 -0
  126. mirascope/api/_generated/organizations/types/organizations_subscription_response_current_plan.py +7 -0
  127. mirascope/api/_generated/organizations/types/organizations_subscription_response_payment_method.py +26 -0
  128. mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change.py +34 -0
  129. mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change_target_plan.py +7 -0
  130. mirascope/api/_generated/organizations/types/organizations_update_response.py +4 -3
  131. mirascope/api/_generated/organizations/types/organizations_update_response_role.py +1 -3
  132. mirascope/api/_generated/organizations/types/organizations_update_subscription_request_target_plan.py +7 -0
  133. mirascope/api/_generated/organizations/types/organizations_update_subscription_response.py +35 -0
  134. mirascope/api/_generated/project_memberships/__init__.py +25 -0
  135. mirascope/api/_generated/project_memberships/client.py +437 -0
  136. mirascope/api/_generated/project_memberships/raw_client.py +1039 -0
  137. mirascope/api/_generated/project_memberships/types/__init__.py +29 -0
  138. mirascope/api/_generated/project_memberships/types/project_memberships_create_request_role.py +7 -0
  139. mirascope/api/_generated/project_memberships/types/project_memberships_create_response.py +35 -0
  140. mirascope/api/_generated/project_memberships/types/project_memberships_create_response_role.py +7 -0
  141. mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item.py +33 -0
  142. mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item_role.py +7 -0
  143. mirascope/api/_generated/project_memberships/types/project_memberships_update_request_role.py +7 -0
  144. mirascope/api/_generated/project_memberships/types/project_memberships_update_response.py +35 -0
  145. mirascope/api/_generated/project_memberships/types/project_memberships_update_response_role.py +7 -0
  146. mirascope/api/_generated/projects/__init__.py +2 -12
  147. mirascope/api/_generated/projects/client.py +17 -71
  148. mirascope/api/_generated/projects/raw_client.py +295 -51
  149. mirascope/api/_generated/projects/types/__init__.py +1 -6
  150. mirascope/api/_generated/projects/types/projects_create_response.py +3 -9
  151. mirascope/api/_generated/projects/types/projects_get_response.py +3 -9
  152. mirascope/api/_generated/projects/types/projects_list_response_item.py +3 -9
  153. mirascope/api/_generated/projects/types/projects_update_response.py +3 -9
  154. mirascope/api/_generated/reference.md +3619 -182
  155. mirascope/api/_generated/tags/__init__.py +19 -0
  156. mirascope/api/_generated/tags/client.py +504 -0
  157. mirascope/api/_generated/tags/raw_client.py +1288 -0
  158. mirascope/api/_generated/tags/types/__init__.py +17 -0
  159. mirascope/api/_generated/tags/types/tags_create_response.py +41 -0
  160. mirascope/api/_generated/tags/types/tags_get_response.py +41 -0
  161. mirascope/api/_generated/tags/types/tags_list_response.py +23 -0
  162. mirascope/api/_generated/tags/types/tags_list_response_tags_item.py +41 -0
  163. mirascope/api/_generated/tags/types/tags_update_response.py +41 -0
  164. mirascope/api/_generated/token_cost/__init__.py +7 -0
  165. mirascope/api/_generated/token_cost/client.py +160 -0
  166. mirascope/api/_generated/token_cost/raw_client.py +264 -0
  167. mirascope/api/_generated/token_cost/types/__init__.py +8 -0
  168. mirascope/api/_generated/token_cost/types/token_cost_calculate_request_usage.py +54 -0
  169. mirascope/api/_generated/token_cost/types/token_cost_calculate_response.py +52 -0
  170. mirascope/api/_generated/traces/__init__.py +42 -0
  171. mirascope/api/_generated/traces/client.py +941 -0
  172. mirascope/api/_generated/traces/raw_client.py +2177 -23
  173. mirascope/api/_generated/traces/types/__init__.py +60 -0
  174. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +4 -11
  175. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +2 -6
  176. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +1 -3
  177. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +8 -24
  178. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +2 -6
  179. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +3 -9
  180. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +2 -6
  181. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +3 -9
  182. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +4 -8
  183. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +2 -6
  184. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +8 -24
  185. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +2 -6
  186. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +3 -9
  187. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value_values_item.py +1 -3
  188. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +6 -18
  189. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +3 -9
  190. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +8 -24
  191. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +2 -6
  192. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +2 -6
  193. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value_values_item.py +1 -3
  194. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +2 -6
  195. mirascope/api/_generated/traces/types/traces_create_response.py +2 -5
  196. mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +3 -9
  197. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response.py +60 -0
  198. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_functions_item.py +24 -0
  199. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_models_item.py +22 -0
  200. mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response.py +33 -0
  201. mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response_spans_item.py +88 -0
  202. mirascope/api/_generated/traces/types/traces_get_trace_detail_response.py +33 -0
  203. mirascope/api/_generated/traces/types/traces_get_trace_detail_response_spans_item.py +88 -0
  204. mirascope/api/_generated/traces/types/traces_list_by_function_hash_response.py +25 -0
  205. mirascope/api/_generated/traces/types/traces_list_by_function_hash_response_traces_item.py +44 -0
  206. mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item.py +26 -0
  207. mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item_operator.py +7 -0
  208. mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_by.py +7 -0
  209. mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_order.py +7 -0
  210. mirascope/api/_generated/traces/types/traces_search_by_env_response.py +26 -0
  211. mirascope/api/_generated/traces/types/traces_search_by_env_response_spans_item.py +50 -0
  212. mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item.py +26 -0
  213. mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item_operator.py +7 -0
  214. mirascope/api/_generated/traces/types/traces_search_request_sort_by.py +7 -0
  215. mirascope/api/_generated/traces/types/traces_search_request_sort_order.py +5 -0
  216. mirascope/api/_generated/traces/types/traces_search_response.py +26 -0
  217. mirascope/api/_generated/traces/types/traces_search_response_spans_item.py +50 -0
  218. mirascope/api/_generated/types/__init__.py +48 -0
  219. mirascope/api/_generated/types/already_exists_error.py +1 -3
  220. mirascope/api/_generated/types/bad_request_error_body.py +50 -0
  221. mirascope/api/_generated/types/click_house_error.py +22 -0
  222. mirascope/api/_generated/types/database_error.py +1 -3
  223. mirascope/api/_generated/types/date.py +3 -0
  224. mirascope/api/_generated/types/http_api_decode_error.py +1 -3
  225. mirascope/api/_generated/types/immutable_resource_error.py +22 -0
  226. mirascope/api/_generated/types/internal_server_error_body.py +49 -0
  227. mirascope/api/_generated/types/issue.py +1 -3
  228. mirascope/api/_generated/types/issue_tag.py +1 -8
  229. mirascope/api/_generated/types/not_found_error_body.py +1 -3
  230. mirascope/api/_generated/types/number_from_string.py +3 -0
  231. mirascope/api/_generated/types/permission_denied_error.py +1 -3
  232. mirascope/api/_generated/types/permission_denied_error_tag.py +1 -3
  233. mirascope/api/_generated/types/plan_limit_exceeded_error.py +32 -0
  234. mirascope/api/_generated/types/plan_limit_exceeded_error_tag.py +7 -0
  235. mirascope/api/_generated/types/pricing_unavailable_error.py +23 -0
  236. mirascope/api/_generated/types/property_key_key.py +1 -3
  237. mirascope/api/_generated/types/rate_limit_error.py +31 -0
  238. mirascope/api/_generated/types/rate_limit_error_tag.py +5 -0
  239. mirascope/api/_generated/types/service_unavailable_error_body.py +24 -0
  240. mirascope/api/_generated/types/service_unavailable_error_tag.py +7 -0
  241. mirascope/api/_generated/types/stripe_error.py +20 -0
  242. mirascope/api/_generated/types/subscription_past_due_error.py +31 -0
  243. mirascope/api/_generated/types/subscription_past_due_error_tag.py +7 -0
  244. mirascope/api/_generated/types/unauthorized_error_body.py +21 -0
  245. mirascope/api/_generated/types/unauthorized_error_tag.py +5 -0
  246. mirascope/api/settings.py +19 -1
  247. mirascope/llm/__init__.py +55 -8
  248. mirascope/llm/calls/__init__.py +2 -1
  249. mirascope/llm/calls/calls.py +3 -1
  250. mirascope/llm/calls/decorator.py +21 -7
  251. mirascope/llm/content/tool_call.py +6 -0
  252. mirascope/llm/content/tool_output.py +22 -5
  253. mirascope/llm/exceptions.py +284 -71
  254. mirascope/llm/formatting/__init__.py +19 -2
  255. mirascope/llm/formatting/format.py +219 -30
  256. mirascope/llm/formatting/output_parser.py +178 -0
  257. mirascope/llm/formatting/partial.py +80 -7
  258. mirascope/llm/formatting/primitives.py +192 -0
  259. mirascope/llm/formatting/types.py +21 -64
  260. mirascope/llm/mcp/__init__.py +2 -2
  261. mirascope/llm/mcp/mcp_client.py +130 -0
  262. mirascope/llm/messages/__init__.py +3 -0
  263. mirascope/llm/messages/_utils.py +34 -0
  264. mirascope/llm/models/__init__.py +5 -0
  265. mirascope/llm/models/models.py +137 -69
  266. mirascope/llm/{providers/base → models}/params.py +16 -37
  267. mirascope/llm/models/thinking_config.py +61 -0
  268. mirascope/llm/prompts/_utils.py +0 -32
  269. mirascope/llm/prompts/decorator.py +16 -5
  270. mirascope/llm/prompts/prompts.py +131 -68
  271. mirascope/llm/providers/__init__.py +18 -2
  272. mirascope/llm/providers/anthropic/__init__.py +3 -21
  273. mirascope/llm/providers/anthropic/_utils/__init__.py +2 -0
  274. mirascope/llm/providers/anthropic/_utils/beta_decode.py +22 -11
  275. mirascope/llm/providers/anthropic/_utils/beta_encode.py +75 -25
  276. mirascope/llm/providers/anthropic/_utils/decode.py +22 -11
  277. mirascope/llm/providers/anthropic/_utils/encode.py +82 -20
  278. mirascope/llm/providers/anthropic/_utils/errors.py +2 -2
  279. mirascope/llm/providers/anthropic/beta_provider.py +64 -18
  280. mirascope/llm/providers/anthropic/provider.py +91 -33
  281. mirascope/llm/providers/base/__init__.py +0 -2
  282. mirascope/llm/providers/base/_utils.py +55 -11
  283. mirascope/llm/providers/base/base_provider.py +116 -37
  284. mirascope/llm/providers/google/__init__.py +2 -17
  285. mirascope/llm/providers/google/_utils/__init__.py +2 -0
  286. mirascope/llm/providers/google/_utils/decode.py +37 -15
  287. mirascope/llm/providers/google/_utils/encode.py +127 -19
  288. mirascope/llm/providers/google/_utils/errors.py +3 -2
  289. mirascope/llm/providers/google/model_info.py +1 -0
  290. mirascope/llm/providers/google/provider.py +68 -19
  291. mirascope/llm/providers/mirascope/__init__.py +5 -0
  292. mirascope/llm/providers/mirascope/_utils.py +73 -0
  293. mirascope/llm/providers/mirascope/provider.py +349 -0
  294. mirascope/llm/providers/mlx/__init__.py +2 -17
  295. mirascope/llm/providers/mlx/_utils.py +8 -3
  296. mirascope/llm/providers/mlx/encoding/base.py +5 -2
  297. mirascope/llm/providers/mlx/encoding/transformers.py +5 -2
  298. mirascope/llm/providers/mlx/mlx.py +23 -6
  299. mirascope/llm/providers/mlx/provider.py +42 -13
  300. mirascope/llm/providers/ollama/__init__.py +1 -13
  301. mirascope/llm/providers/openai/_utils/errors.py +2 -2
  302. mirascope/llm/providers/openai/completions/__init__.py +2 -20
  303. mirascope/llm/providers/openai/completions/_utils/decode.py +14 -3
  304. mirascope/llm/providers/openai/completions/_utils/encode.py +35 -28
  305. mirascope/llm/providers/openai/completions/base_provider.py +40 -11
  306. mirascope/llm/providers/openai/provider.py +40 -10
  307. mirascope/llm/providers/openai/responses/__init__.py +1 -17
  308. mirascope/llm/providers/openai/responses/_utils/__init__.py +2 -0
  309. mirascope/llm/providers/openai/responses/_utils/decode.py +21 -8
  310. mirascope/llm/providers/openai/responses/_utils/encode.py +59 -19
  311. mirascope/llm/providers/openai/responses/provider.py +56 -18
  312. mirascope/llm/providers/provider_id.py +1 -0
  313. mirascope/llm/providers/provider_registry.py +96 -19
  314. mirascope/llm/providers/together/__init__.py +1 -13
  315. mirascope/llm/responses/__init__.py +6 -1
  316. mirascope/llm/responses/_utils.py +102 -12
  317. mirascope/llm/responses/base_response.py +5 -2
  318. mirascope/llm/responses/base_stream_response.py +139 -45
  319. mirascope/llm/responses/response.py +2 -1
  320. mirascope/llm/responses/root_response.py +89 -17
  321. mirascope/llm/responses/stream_response.py +6 -9
  322. mirascope/llm/tools/decorator.py +17 -8
  323. mirascope/llm/tools/tool_schema.py +43 -10
  324. mirascope/llm/tools/toolkit.py +35 -27
  325. mirascope/llm/tools/tools.py +123 -30
  326. mirascope/ops/__init__.py +64 -109
  327. mirascope/ops/_internal/configuration.py +82 -31
  328. mirascope/ops/_internal/exporters/exporters.py +64 -11
  329. mirascope/ops/_internal/instrumentation/llm/common.py +530 -0
  330. mirascope/ops/_internal/instrumentation/llm/cost.py +190 -0
  331. mirascope/ops/_internal/instrumentation/llm/encode.py +1 -1
  332. mirascope/ops/_internal/instrumentation/llm/llm.py +116 -1243
  333. mirascope/ops/_internal/instrumentation/llm/model.py +1798 -0
  334. mirascope/ops/_internal/instrumentation/llm/response.py +521 -0
  335. mirascope/ops/_internal/instrumentation/llm/serialize.py +300 -0
  336. mirascope/ops/_internal/protocols.py +83 -1
  337. mirascope/ops/_internal/traced_calls.py +4 -0
  338. mirascope/ops/_internal/traced_functions.py +141 -12
  339. mirascope/ops/_internal/tracing.py +78 -1
  340. mirascope/ops/_internal/utils.py +52 -4
  341. mirascope/ops/_internal/versioned_functions.py +54 -43
  342. {mirascope-2.0.0a5.dist-info → mirascope-2.0.1.dist-info}/METADATA +14 -13
  343. mirascope-2.0.1.dist-info/RECORD +423 -0
  344. {mirascope-2.0.0a5.dist-info → mirascope-2.0.1.dist-info}/licenses/LICENSE +1 -1
  345. mirascope/llm/formatting/_utils.py +0 -78
  346. mirascope/llm/mcp/client.py +0 -118
  347. mirascope/llm/providers/_missing_import_stubs.py +0 -49
  348. mirascope-2.0.0a5.dist-info/RECORD +0 -265
  349. {mirascope-2.0.0a5.dist-info → mirascope-2.0.1.dist-info}/WHEEL +0 -0
@@ -1,20 +1,34 @@
1
- def extract_serialized_json(text: str) -> str:
2
- """Extract the serialized JSON string from text that may contain extra content.
1
+ """Utilities for response classes."""
2
+
3
+ import json
4
+ from typing import cast
5
+
6
+ import jiter
7
+ from pydantic import BaseModel
8
+
9
+ from ..formatting import (
10
+ FormattableT,
11
+ Partial,
12
+ PrimitiveWrapperModel,
13
+ create_wrapper_model,
14
+ is_primitive_type,
15
+ )
16
+
17
+
18
+ def _strip_json_preamble(text: str) -> str | None:
19
+ """Strip preamble text before JSON content.
3
20
 
4
21
  Handles cases where models output text before JSON like:
5
- "Sure thing! Here's the JSON:\n{...}"
22
+ "Sure thing! Here's the JSON:\n{..."
6
23
 
7
24
  Or cases where the model wraps the JSON in code blocks like:
8
- "```json\n{...}\n```"
25
+ "```json\n{..."
9
26
 
10
27
  Args:
11
28
  text: The raw text that may contain a JSON object
12
29
 
13
- Raises:
14
- ValueError: If no serialized json object string was found.
15
-
16
30
  Returns:
17
- The extracted serialized JSON string
31
+ Text starting from the opening `{`, or None if no `{` found.
18
32
  """
19
33
  code_block_start_marker = "```json"
20
34
  code_block_start = text.find(code_block_start_marker)
@@ -25,14 +39,39 @@ def extract_serialized_json(text: str) -> str:
25
39
 
26
40
  json_start = text.find("{")
27
41
  if json_start == -1:
28
- raise ValueError("Could not extract json: no opening `{`")
42
+ return None
43
+
44
+ return text[json_start:]
45
+
46
+
47
+ def extract_serialized_json(text: str) -> str:
48
+ """Extract the serialized JSON string from text that may contain extra content.
49
+
50
+ Handles cases where models output text before JSON like:
51
+ "Sure thing! Here's the JSON:\n{...}"
52
+
53
+ Or cases where the model wraps the JSON in code blocks like:
54
+ "```json\n{...}\n```"
55
+
56
+ Args:
57
+ text: The raw text that may contain a JSON object
58
+
59
+ Raises:
60
+ json.JSONDecodeError: If no valid JSON object could be extracted.
61
+
62
+ Returns:
63
+ The extracted serialized JSON string
64
+ """
65
+ stripped = _strip_json_preamble(text)
66
+ if stripped is None:
67
+ raise json.JSONDecodeError("No JSON object found: missing '{'", text, 0)
29
68
 
30
69
  # Find the matching closing brace
31
70
  brace_count = 0
32
71
  in_string = False
33
72
  escaped = False
34
73
 
35
- for i, char in enumerate(text[json_start:], json_start):
74
+ for i, char in enumerate(stripped):
36
75
  if escaped:
37
76
  escaped = False
38
77
  continue
@@ -51,6 +90,57 @@ def extract_serialized_json(text: str) -> str:
51
90
  elif char == "}":
52
91
  brace_count -= 1
53
92
  if brace_count == 0:
54
- return text[json_start : i + 1]
93
+ return stripped[: i + 1]
94
+
95
+ raise json.JSONDecodeError("No JSON object found: missing '}'", text, len(text))
96
+
97
+
98
+ def parse_partial_json(
99
+ json_text: str, formattable: type[FormattableT]
100
+ ) -> FormattableT | Partial[FormattableT] | None:
101
+ """Parse incomplete JSON into a Partial model for structured streaming.
102
+
103
+ Uses jiter's partial mode to handle incomplete JSON gracefully.
104
+ Returns None if JSON cannot be parsed yet.
105
+
106
+ Handles cases where models output text before JSON like:
107
+ "Sure thing! Here's the JSON:\n{..."
108
+
109
+ Args:
110
+ json_text: The incomplete JSON string to parse
111
+ formattable: The target format type (BaseModel or PrimitiveType)
112
+
113
+ Returns:
114
+ Parsed partial object, or None if unparsable
115
+
116
+ Example:
117
+ >>> from pydantic import BaseModel
118
+ >>> class Book(BaseModel):
119
+ ... title: str
120
+ ... author: str
121
+ >>> parse_partial_json('{"title": "The Name"', Book)
122
+ PartialBook(title='The Name', author=None)
123
+ """
124
+ # Strip preamble text before JSON
125
+ stripped = _strip_json_preamble(json_text)
126
+ if stripped is None:
127
+ return None
128
+
129
+ try:
130
+ parsed = jiter.from_json(stripped.encode(), partial_mode="trailing-strings")
131
+ except Exception:
132
+ return None
133
+
134
+ target_model = formattable
135
+ if is_primitive_type(target_model):
136
+ target_model = cast(BaseModel, create_wrapper_model(target_model))
137
+
138
+ try:
139
+ instance = cast(BaseModel, Partial[target_model]).model_validate(parsed)
140
+ except Exception:
141
+ return None
142
+
143
+ if is_primitive_type(formattable):
144
+ return cast(PrimitiveWrapperModel, instance).output
55
145
 
56
- raise ValueError("Could not extract json: no closing `}`")
146
+ return cast(Partial[FormattableT], instance)
@@ -1,7 +1,7 @@
1
1
  """Shared base of Response and AsyncResponse."""
2
2
 
3
3
  from collections.abc import Sequence
4
- from typing import TYPE_CHECKING, Any
4
+ from typing import TYPE_CHECKING, Any, TypeVar
5
5
 
6
6
  from ..content import Text, Thought, ToolCall
7
7
  from ..formatting import Format, FormattableT
@@ -12,7 +12,10 @@ from .root_response import RootResponse
12
12
  from .usage import Usage
13
13
 
14
14
  if TYPE_CHECKING:
15
- from ..providers import ModelId, Params, ProviderId
15
+ from ..models import Params
16
+ from ..providers import ModelId, ProviderId
17
+
18
+ ResponseT = TypeVar("ResponseT", bound="BaseResponse[Any, Any]")
16
19
 
17
20
 
18
21
  class BaseResponse(RootResponse[ToolkitT, FormattableT]):
@@ -20,7 +20,12 @@ from ..content import (
20
20
  ToolCallEndChunk,
21
21
  ToolCallStartChunk,
22
22
  )
23
- from ..formatting import Format, FormattableT, Partial
23
+ from ..formatting import (
24
+ Format,
25
+ FormattableT,
26
+ Partial,
27
+ is_output_parser,
28
+ )
24
29
  from ..messages import AssistantMessage, Message
25
30
  from ..tools import FORMAT_TOOL_NAME, ToolkitT
26
31
  from ..types import Jsonable
@@ -39,7 +44,11 @@ from .streams import (
39
44
  from .usage import Usage, UsageDeltaChunk
40
45
 
41
46
  if TYPE_CHECKING:
42
- from ..providers import ModelId, Params, ProviderId
47
+ from ..models import Params
48
+ from ..providers import ModelId, ProviderId
49
+
50
+
51
+ StreamResponseT = TypeVar("StreamResponseT", bound="BaseStreamResponse[Any, Any, Any]")
43
52
 
44
53
 
45
54
  @dataclass(kw_only=True)
@@ -227,7 +236,8 @@ class BaseStreamResponse(
227
236
  self.messages = list(input_messages) + [self._assistant_message]
228
237
 
229
238
  self._chunk_iterator = chunk_iterator
230
- self._current_content: Text | Thought | ToolCall | None = None
239
+ self._current_content: Text | Thought | None = None
240
+ self._current_tool_calls: dict[str, ToolCall] = {}
231
241
 
232
242
  self._processing_format_tool: bool = False
233
243
 
@@ -269,7 +279,7 @@ class BaseStreamResponse(
269
279
  self, chunk: TextStartChunk | TextChunk | TextEndChunk
270
280
  ) -> None:
271
281
  if chunk.type == "text_start_chunk":
272
- if self._current_content:
282
+ if self._current_content or self._current_tool_calls:
273
283
  raise RuntimeError(
274
284
  "Received text_start_chunk while processing another chunk"
275
285
  )
@@ -292,7 +302,7 @@ class BaseStreamResponse(
292
302
  self, chunk: ThoughtStartChunk | ThoughtChunk | ThoughtEndChunk
293
303
  ) -> None:
294
304
  if chunk.type == "thought_start_chunk":
295
- if self._current_content:
305
+ if self._current_content or self._current_tool_calls:
296
306
  raise RuntimeError(
297
307
  "Received thought_start_chunk while processing another chunk"
298
308
  )
@@ -323,35 +333,38 @@ class BaseStreamResponse(
323
333
  raise RuntimeError(
324
334
  "Received tool_call_start_chunk while processing another chunk"
325
335
  )
326
- self._current_content = ToolCall(
336
+ if chunk.id in self._current_tool_calls:
337
+ raise RuntimeError("Got tool_call_start_chunk with conflicting id")
338
+ # Create a new tool call and track it by ID
339
+ # Multiple tool calls can be in progress simultaneously (interleaved)
340
+ tool_call = ToolCall(
327
341
  id=chunk.id,
328
342
  name=chunk.name,
329
343
  args="",
330
344
  )
345
+ self._current_tool_calls[chunk.id] = tool_call
331
346
 
332
347
  elif chunk.type == "tool_call_chunk":
333
- if (
334
- self._current_content is None
335
- or self._current_content.type != "tool_call"
336
- ):
348
+ # Look up the tool call by ID
349
+ tool_call = self._current_tool_calls.get(chunk.id)
350
+ if tool_call is None:
337
351
  raise RuntimeError(
338
- "Received tool_call_chunk while not processing tool call."
352
+ f"Received tool_call_chunk for unknown tool call ID: {chunk.id}"
339
353
  )
340
- self._current_content.args += chunk.delta
354
+ tool_call.args += chunk.delta
341
355
 
342
356
  elif chunk.type == "tool_call_end_chunk":
343
- if (
344
- self._current_content is None
345
- or self._current_content.type != "tool_call"
346
- ):
357
+ # Finalize the tool call
358
+ tool_call = self._current_tool_calls.get(chunk.id)
359
+ if tool_call is None:
347
360
  raise RuntimeError(
348
- "Received tool_call_end_chunk while not processing tool call."
361
+ f"Received tool_call_end_chunk for unknown tool call ID: {chunk.id}"
349
362
  )
350
- if not self._current_content.args:
351
- self._current_content.args = "{}"
352
- self._content.append(self._current_content)
353
- self._tool_calls.append(self._current_content)
354
- self._current_content = None
363
+ if not tool_call.args:
364
+ tool_call.args = "{}"
365
+ self._content.append(tool_call)
366
+ self._tool_calls.append(tool_call)
367
+ del self._current_tool_calls[chunk.id]
355
368
 
356
369
  def _pretty_chunk(self, chunk: AssistantContentChunk, spacer: str) -> str:
357
370
  match chunk.type:
@@ -501,6 +514,26 @@ class BaseSyncStreamResponse(BaseStreamResponse[ChunkIterator, ToolkitT, Formatt
501
514
  for _ in self.chunk_stream():
502
515
  pass
503
516
 
517
+ def text_stream(self, sep: str = "\n") -> Iterator[str]:
518
+ """Stream only the text content from the response.
519
+
520
+ Args:
521
+ sep: Separator to yield between text parts. Defaults to newline.
522
+
523
+ Returns:
524
+ Iterator[str]: Iterator yielding text delta strings
525
+
526
+ Yields text deltas as they arrive, ignoring thoughts, tool calls, and other
527
+ content types. Ideal for displaying text to users in real-time.
528
+
529
+ If you concatenate the strings from `.text_stream()`, it will be equivalent to
530
+ calling `.text(sep=sep)` on the fully consumed response.
531
+ """
532
+ for stream in self.streams():
533
+ if stream.content_type == "text":
534
+ yield from stream
535
+ yield sep
536
+
504
537
  def pretty_stream(self) -> Iterator[str]:
505
538
  """Stream a readable representation of the stream_response as text.
506
539
 
@@ -523,26 +556,46 @@ class BaseSyncStreamResponse(BaseStreamResponse[ChunkIterator, ToolkitT, Formatt
523
556
  printed = True
524
557
  yield pretty
525
558
 
526
- if not printed:
527
- yield "**[No Content]**"
528
-
529
559
  def structured_stream(
530
560
  self,
531
561
  ) -> Iterator[Partial[FormattableT]]:
532
- """Returns an iterator that yields partial structured objects as content streams.
562
+ """Drive the stream forward, yielding partial formatted outputs as they arrive.
533
563
 
534
- Returns:
535
- Iterator[Partial[FormatT]]: Synchronous iterator yielding partial structured objects
564
+ This method consumes the underlying stream chunk by chunk, yielding parsed
565
+ partial outputs each time new content arrives. Each yielded value is a
566
+ Partial[FormattableT] with optional fields that may be None until fully received.
536
567
 
537
- This method yields Partial[FormatT] objects as the response content is streamed,
538
- allowing you to access partial structured data before the response is fully complete.
539
- Each yielded object represents the current state of the parsed structure with all
540
- fields optional.
568
+ Example:
569
+ >>> response = recommend_book.stream("fantasy")
570
+ >>> for partial in response.structured_stream():
571
+ >>> print(f"Title so far: {partial.title}")
572
+ >>> book = response.parse() # Get final complete result
541
573
 
542
574
  Fully iterating through this iterator will fully consume the underlying stream,
543
575
  updating the Response with all collected content.
576
+
577
+ Yields:
578
+ Partial[FormattableT]: Partial objects with fields populated as they arrive.
579
+ Fields not yet received will be None.
580
+
581
+ Raises:
582
+ ValueError: If format parameter not set.
583
+ NotImplementedError: If format uses OutputParser (not supported).
544
584
  """
545
- raise NotImplementedError()
585
+ if self.format is None:
586
+ raise ValueError("structured_stream() requires format parameter")
587
+
588
+ if is_output_parser(self.format.formattable):
589
+ raise NotImplementedError(
590
+ "structured_stream() not supported for OutputParser. "
591
+ "Use BaseModel or primitive types."
592
+ )
593
+
594
+ for chunk in self.chunk_stream():
595
+ if chunk.type == "text_chunk":
596
+ partial = self.parse(partial=True)
597
+ if partial:
598
+ yield partial
546
599
 
547
600
 
548
601
  class BaseAsyncStreamResponse(
@@ -682,6 +735,27 @@ class BaseAsyncStreamResponse(
682
735
  async for _ in self.chunk_stream():
683
736
  pass
684
737
 
738
+ async def text_stream(self, sep: str = "\n") -> AsyncIterator[str]:
739
+ """Stream only the text content from the response.
740
+
741
+ Args:
742
+ sep: Separator to yield between text parts. Defaults to newline.
743
+
744
+ Returns:
745
+ AsyncIterator[str]: Async iterator yielding text delta strings
746
+
747
+ Yields text deltas as they arrive, ignoring thoughts, tool calls, and other
748
+ content types. Ideal for displaying text to users in real-time.
749
+
750
+ If you concatenate the strings from `.text_stream()`, it will be equivalent to
751
+ calling `.text(sep=sep)` on the fully consumed response.
752
+ """
753
+ async for stream in self.streams():
754
+ if stream.content_type == "text":
755
+ async for delta in stream:
756
+ yield delta
757
+ yield sep
758
+
685
759
  async def pretty_stream(self) -> AsyncIterator[str]:
686
760
  """Stream a readable representation of the stream_response as text.
687
761
 
@@ -704,23 +778,43 @@ class BaseAsyncStreamResponse(
704
778
  printed = True
705
779
  yield pretty
706
780
 
707
- if not printed:
708
- yield "**[No Content]**"
709
-
710
- def structured_stream(
781
+ async def structured_stream(
711
782
  self,
712
783
  ) -> AsyncIterator[Partial[FormattableT]]:
713
- """Returns an async iterator that yields partial structured objects as content streams.
784
+ """Drive the stream forward, yielding partial formatted outputs as they arrive.
714
785
 
715
- Returns:
716
- AsyncIterator[Partial[FormatT]]: Async iterator yielding partial structured objects
786
+ This method consumes the underlying stream chunk by chunk, yielding parsed
787
+ partial outputs each time new content arrives. Each yielded value is a
788
+ Partial[FormattableT] with optional fields that may be None until fully received.
717
789
 
718
- This method yields Partial[FormatT] objects as the response content is streamed,
719
- allowing you to access partial structured data before the response is fully complete.
720
- Each yielded object represents the current state of the parsed structure with all
721
- fields optional.
790
+ Example:
791
+ >>> response = await recommend_book.stream("fantasy")
792
+ >>> async for partial in response.structured_stream():
793
+ >>> print(f"Title so far: {partial.title}")
794
+ >>> book = response.parse() # Get final complete result
722
795
 
723
796
  Fully iterating through this iterator will fully consume the underlying stream,
724
797
  updating the Response with all collected content.
798
+
799
+ Yields:
800
+ Partial[FormattableT]: Partial objects with fields populated as they arrive.
801
+ Fields not yet received will be None.
802
+
803
+ Raises:
804
+ ValueError: If format parameter not set.
805
+ NotImplementedError: If format uses OutputParser (not supported).
725
806
  """
726
- raise NotImplementedError()
807
+ if self.format is None:
808
+ raise ValueError("structured_stream() requires format parameter")
809
+
810
+ if is_output_parser(self.format.formattable):
811
+ raise NotImplementedError(
812
+ "structured_stream() not supported for OutputParser. "
813
+ "Use BaseModel or primitive types."
814
+ )
815
+
816
+ async for chunk in self.chunk_stream():
817
+ if chunk.type == "text_chunk":
818
+ partial = self.parse(partial=True)
819
+ if partial:
820
+ yield partial
@@ -24,7 +24,8 @@ from .finish_reason import FinishReason
24
24
  from .usage import Usage
25
25
 
26
26
  if TYPE_CHECKING:
27
- from ..providers import ModelId, Params, ProviderId
27
+ from ..models import Params
28
+ from ..providers import ModelId, ProviderId
28
29
 
29
30
 
30
31
  class Response(BaseResponse[Toolkit, FormattableT]):
@@ -3,10 +3,18 @@
3
3
  from abc import ABC
4
4
  from collections.abc import Sequence
5
5
  from types import NoneType
6
- from typing import TYPE_CHECKING, Any, Generic, Literal, overload
6
+ from typing import TYPE_CHECKING, Any, Generic, Literal, TypeAlias, overload
7
7
 
8
8
  from ..content import AssistantContentPart, Text, Thought, ToolCall
9
- from ..formatting import Format, FormattableT, Partial
9
+ from ..exceptions import ParseError
10
+ from ..formatting import (
11
+ Format,
12
+ FormattableT,
13
+ Partial,
14
+ create_wrapper_model,
15
+ is_output_parser,
16
+ is_primitive_type,
17
+ )
10
18
  from ..messages import Message
11
19
  from ..tools import ToolkitT
12
20
  from . import _utils
@@ -14,8 +22,10 @@ from .finish_reason import FinishReason
14
22
  from .usage import Usage
15
23
 
16
24
  if TYPE_CHECKING:
17
- from ..models import Model
18
- from ..providers import ModelId, Params, ProviderId
25
+ from ..models import Model, Params
26
+ from ..providers import ModelId, ProviderId
27
+
28
+ AnyResponse: TypeAlias = "RootResponse[Any, Any]"
19
29
 
20
30
 
21
31
  class RootResponse(Generic[ToolkitT, FormattableT], ABC):
@@ -107,31 +117,96 @@ class RootResponse(Generic[ToolkitT, FormattableT], ABC):
107
117
  ) -> FormattableT | Partial[FormattableT] | None:
108
118
  """Format the response according to the response format parser.
109
119
 
120
+ Args:
121
+ partial: If True, parse incomplete JSON as Partial model. Only works with
122
+ streaming responses that have accumulated JSON. Returns None if JSON
123
+ is not yet available or cannot be parsed.
124
+
125
+ Supports:
126
+ - Pydantic BaseModel types (JSON schema validation)
127
+ - Primitive types (automatically unwrapped from wrapper model)
128
+ - Custom OutputParsers (custom parsing logic)
129
+ - Partial parsing during streaming (when partial=True)
130
+
110
131
  Returns:
111
- The formatted response object of type FormatT.
132
+ The formatted response object of type FormatT. For BaseModel types, returns
133
+ the model instance. For primitive types, returns the unwrapped value (e.g.,
134
+ a string, list, dict, etc.). For OutputParsers, returns whatever the parser
135
+ returns. When partial=True, returns None if JSON is incomplete or unparsable.
112
136
 
113
137
  Raises:
114
- json.JSONDecodeError: If the response's textual content can't be parsed as
115
- JSON.
116
- pydantic.ValidationError: If the response's content fails validation for the
117
- format type.
138
+ NotImplementedError: If partial=True with OutputParser.
139
+ ParseError: If parsing fails. The `original_exception` attribute contains the
140
+ underlying error (ValueError for JSON extraction, json.JSONDecodeError
141
+ for invalid JSON, pydantic.ValidationError for schema validation, or
142
+ any exception from a custom OutputParser).
118
143
  """
119
144
  if self.format is None:
120
145
  return None
121
146
 
122
147
  formattable = self.format.formattable
148
+
149
+ if is_output_parser(formattable):
150
+ if partial:
151
+ raise NotImplementedError(
152
+ "parse(partial=True) not supported with OutputParser. "
153
+ "Use BaseModel or primitive types."
154
+ )
155
+ try:
156
+ return formattable(self)
157
+ except Exception as e:
158
+ raise ParseError(
159
+ f"OutputParser failed: {e}",
160
+ original_exception=e,
161
+ ) from e
162
+
123
163
  if formattable is None or formattable is NoneType: # pyright: ignore[reportUnnecessaryComparison]
124
164
  # note: pyright claims the None comparison is unnecessary, but removing it
125
165
  # introduces type errors.
126
166
  return None # pragma: no cover
127
167
 
128
- if partial:
129
- raise NotImplementedError
168
+ text = self.text("")
130
169
 
131
- text = "".join(text.text for text in self.texts)
132
- json_text = _utils.extract_serialized_json(text)
170
+ if partial:
171
+ return _utils.parse_partial_json(text, formattable)
172
+ else:
173
+ try:
174
+ json_text = _utils.extract_serialized_json(text)
175
+ if is_primitive_type(formattable):
176
+ wrapper_model = create_wrapper_model(formattable)
177
+ wrapper_instance = wrapper_model.model_validate_json(json_text)
178
+ return wrapper_instance.output
179
+
180
+ return formattable.model_validate_json(json_text)
181
+ except Exception as e:
182
+ raise ParseError(
183
+ f"Failed to parse response: {e}",
184
+ original_exception=e,
185
+ ) from e
186
+
187
+ def text(self, sep: str = "\n") -> str:
188
+ """Return all text content from this response as a single string.
189
+
190
+ Joins the text from all `Text` parts in the response content using the
191
+ specified separator.
192
+
193
+ Args:
194
+ sep: The separator to use when joining multiple text parts.
195
+ Defaults to newline ("\\n").
133
196
 
134
- return formattable.model_validate_json(json_text)
197
+ Returns:
198
+ A string containing all text content joined by the separator.
199
+ Returns an empty string if the response contains no text parts.
200
+
201
+ Example:
202
+ >>> response.text() # Join with newlines (default)
203
+ 'Hello\\nWorld'
204
+ >>> response.text(sep=" ") # Join with spaces
205
+ 'Hello World'
206
+ >>> response.text(sep="") # Concatenate directly
207
+ 'HelloWorld'
208
+ """
209
+ return sep.join(text.text for text in self.texts)
135
210
 
136
211
  def pretty(self) -> str:
137
212
  """Return a string representation of all response content.
@@ -147,9 +222,6 @@ class RootResponse(Generic[ToolkitT, FormattableT], ABC):
147
222
 
148
223
  I am going to use the calculator and answer your question for you!
149
224
  """
150
- if not self.content:
151
- return "**[No Content]**"
152
-
153
225
  pretty_parts: list[str] = []
154
226
  for part in self.content:
155
227
  if isinstance(part, Text):
@@ -27,7 +27,8 @@ from .base_stream_response import (
27
27
  )
28
28
 
29
29
  if TYPE_CHECKING:
30
- from ..providers import ModelId, Params, ProviderId
30
+ from ..models import Params
31
+ from ..providers import ModelId, ProviderId
31
32
 
32
33
 
33
34
  class StreamResponse(BaseSyncStreamResponse[Toolkit, FormattableT]):
@@ -85,9 +86,8 @@ class StreamResponse(BaseSyncStreamResponse[Toolkit, FormattableT]):
85
86
 
86
87
  stream_response = answer_question.stream("What is the capital of France?")
87
88
 
88
- for chunk in stream_response.pretty_stream():
89
+ for chunk in stream_response.text_stream():
89
90
  print(chunk, end="", flush=True)
90
- print()
91
91
  ```
92
92
  """
93
93
 
@@ -212,9 +212,8 @@ class AsyncStreamResponse(BaseAsyncStreamResponse[AsyncToolkit, FormattableT]):
212
212
 
213
213
  stream_response = await answer_question.stream("What is the capital of France?")
214
214
 
215
- async for chunk in stream_response.pretty_stream():
215
+ async for chunk in stream_response.text_stream():
216
216
  print(chunk, end="", flush=True)
217
- print()
218
217
  ```
219
218
  """
220
219
 
@@ -348,9 +347,8 @@ class ContextStreamResponse(
348
347
  ctx = llm.Context()
349
348
  stream_response = answer_question.stream(ctx, "What is the capital of France?")
350
349
 
351
- for chunk in stream_response.pretty_stream():
350
+ for chunk in stream_response.text_stream():
352
351
  print(chunk, end="", flush=True)
353
- print()
354
352
  ```
355
353
  """
356
354
 
@@ -492,9 +490,8 @@ class AsyncContextStreamResponse(
492
490
  ctx = llm.Context()
493
491
  stream_response = await answer_question.stream(ctx, "What is the capital of France?")
494
492
 
495
- async for chunk in stream_response.pretty_stream():
493
+ async for chunk in stream_response.text_stream():
496
494
  print(chunk, end="", flush=True)
497
- print()
498
495
  ```
499
496
  """
500
497