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.
- mirascope/__init__.py +10 -1
- mirascope/_stubs.py +363 -0
- mirascope/api/__init__.py +8 -0
- mirascope/api/_generated/__init__.py +285 -2
- mirascope/api/_generated/annotations/__init__.py +33 -0
- mirascope/api/_generated/annotations/client.py +506 -0
- mirascope/api/_generated/annotations/raw_client.py +1414 -0
- mirascope/api/_generated/annotations/types/__init__.py +31 -0
- mirascope/api/_generated/annotations/types/annotations_create_request_label.py +5 -0
- mirascope/api/_generated/annotations/types/annotations_create_response.py +48 -0
- mirascope/api/_generated/annotations/types/annotations_create_response_label.py +5 -0
- mirascope/api/_generated/annotations/types/annotations_get_response.py +48 -0
- mirascope/api/_generated/annotations/types/annotations_get_response_label.py +5 -0
- mirascope/api/_generated/annotations/types/annotations_list_request_label.py +5 -0
- mirascope/api/_generated/annotations/types/annotations_list_response.py +21 -0
- mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item.py +50 -0
- mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item_label.py +5 -0
- mirascope/api/_generated/annotations/types/annotations_update_request_label.py +5 -0
- mirascope/api/_generated/annotations/types/annotations_update_response.py +48 -0
- mirascope/api/_generated/annotations/types/annotations_update_response_label.py +5 -0
- mirascope/api/_generated/api_keys/__init__.py +12 -2
- mirascope/api/_generated/api_keys/client.py +77 -0
- mirascope/api/_generated/api_keys/raw_client.py +422 -39
- mirascope/api/_generated/api_keys/types/__init__.py +7 -1
- mirascope/api/_generated/api_keys/types/api_keys_create_response.py +4 -12
- mirascope/api/_generated/api_keys/types/api_keys_get_response.py +4 -12
- mirascope/api/_generated/api_keys/types/api_keys_list_all_for_org_response_item.py +40 -0
- mirascope/api/_generated/api_keys/types/api_keys_list_response_item.py +4 -12
- mirascope/api/_generated/client.py +42 -0
- mirascope/api/_generated/core/client_wrapper.py +2 -14
- mirascope/api/_generated/core/datetime_utils.py +1 -3
- mirascope/api/_generated/core/file.py +2 -5
- mirascope/api/_generated/core/http_client.py +36 -112
- mirascope/api/_generated/core/jsonable_encoder.py +1 -3
- mirascope/api/_generated/core/pydantic_utilities.py +19 -74
- mirascope/api/_generated/core/query_encoder.py +1 -3
- mirascope/api/_generated/core/serialization.py +4 -10
- mirascope/api/_generated/docs/client.py +2 -6
- mirascope/api/_generated/docs/raw_client.py +51 -5
- mirascope/api/_generated/environment.py +3 -3
- mirascope/api/_generated/environments/__init__.py +6 -0
- mirascope/api/_generated/environments/client.py +117 -0
- mirascope/api/_generated/environments/raw_client.py +530 -51
- mirascope/api/_generated/environments/types/__init__.py +10 -0
- mirascope/api/_generated/environments/types/environments_create_response.py +1 -3
- mirascope/api/_generated/environments/types/environments_get_analytics_response.py +60 -0
- mirascope/api/_generated/environments/types/environments_get_analytics_response_top_functions_item.py +24 -0
- mirascope/api/_generated/environments/types/environments_get_analytics_response_top_models_item.py +22 -0
- mirascope/api/_generated/environments/types/environments_get_response.py +1 -3
- mirascope/api/_generated/environments/types/environments_list_response_item.py +1 -3
- mirascope/api/_generated/environments/types/environments_update_response.py +1 -3
- mirascope/api/_generated/errors/__init__.py +8 -0
- mirascope/api/_generated/errors/bad_request_error.py +1 -2
- mirascope/api/_generated/errors/conflict_error.py +1 -2
- mirascope/api/_generated/errors/forbidden_error.py +1 -5
- mirascope/api/_generated/errors/internal_server_error.py +1 -6
- mirascope/api/_generated/errors/not_found_error.py +1 -5
- mirascope/api/_generated/errors/payment_required_error.py +15 -0
- mirascope/api/_generated/errors/service_unavailable_error.py +14 -0
- mirascope/api/_generated/errors/too_many_requests_error.py +15 -0
- mirascope/api/_generated/errors/unauthorized_error.py +11 -0
- mirascope/api/_generated/functions/__init__.py +39 -0
- mirascope/api/_generated/functions/client.py +647 -0
- mirascope/api/_generated/functions/raw_client.py +1890 -0
- mirascope/api/_generated/functions/types/__init__.py +53 -0
- mirascope/api/_generated/functions/types/functions_create_request_dependencies_value.py +20 -0
- mirascope/api/_generated/functions/types/functions_create_response.py +37 -0
- mirascope/api/_generated/functions/types/functions_create_response_dependencies_value.py +20 -0
- mirascope/api/_generated/functions/types/functions_find_by_hash_response.py +39 -0
- mirascope/api/_generated/functions/types/functions_find_by_hash_response_dependencies_value.py +20 -0
- mirascope/api/_generated/functions/types/functions_get_by_env_response.py +53 -0
- mirascope/api/_generated/functions/types/functions_get_by_env_response_dependencies_value.py +22 -0
- mirascope/api/_generated/functions/types/functions_get_response.py +37 -0
- mirascope/api/_generated/functions/types/functions_get_response_dependencies_value.py +20 -0
- mirascope/api/_generated/functions/types/functions_list_by_env_response.py +25 -0
- mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item.py +56 -0
- mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item_dependencies_value.py +22 -0
- mirascope/api/_generated/functions/types/functions_list_response.py +21 -0
- mirascope/api/_generated/functions/types/functions_list_response_functions_item.py +41 -0
- mirascope/api/_generated/functions/types/functions_list_response_functions_item_dependencies_value.py +20 -0
- mirascope/api/_generated/health/client.py +2 -6
- mirascope/api/_generated/health/raw_client.py +51 -5
- mirascope/api/_generated/health/types/health_check_response.py +1 -3
- mirascope/api/_generated/organization_invitations/__init__.py +33 -0
- mirascope/api/_generated/organization_invitations/client.py +546 -0
- mirascope/api/_generated/organization_invitations/raw_client.py +1519 -0
- mirascope/api/_generated/organization_invitations/types/__init__.py +53 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response.py +34 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response_role.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_create_request_role.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response.py +48 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_role.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_status.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response.py +48 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_role.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_status.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item.py +48 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_role.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_status.py +7 -0
- mirascope/api/_generated/organization_memberships/__init__.py +19 -0
- mirascope/api/_generated/organization_memberships/client.py +302 -0
- mirascope/api/_generated/organization_memberships/raw_client.py +736 -0
- mirascope/api/_generated/organization_memberships/types/__init__.py +27 -0
- mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item.py +33 -0
- mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item_role.py +7 -0
- mirascope/api/_generated/organization_memberships/types/organization_memberships_update_request_role.py +7 -0
- mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response.py +31 -0
- mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response_role.py +7 -0
- mirascope/api/_generated/organizations/__init__.py +26 -0
- mirascope/api/_generated/organizations/client.py +465 -0
- mirascope/api/_generated/organizations/raw_client.py +1799 -108
- mirascope/api/_generated/organizations/types/__init__.py +48 -0
- mirascope/api/_generated/organizations/types/organizations_create_payment_intent_response.py +24 -0
- mirascope/api/_generated/organizations/types/organizations_create_response.py +4 -3
- mirascope/api/_generated/organizations/types/organizations_create_response_role.py +1 -3
- mirascope/api/_generated/organizations/types/organizations_get_response.py +4 -3
- mirascope/api/_generated/organizations/types/organizations_get_response_role.py +1 -3
- mirascope/api/_generated/organizations/types/organizations_list_response_item.py +4 -3
- mirascope/api/_generated/organizations/types/organizations_list_response_item_role.py +1 -3
- mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_request_target_plan.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response.py +47 -0
- mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item.py +33 -0
- mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item_resource.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_router_balance_response.py +24 -0
- mirascope/api/_generated/organizations/types/organizations_subscription_response.py +53 -0
- mirascope/api/_generated/organizations/types/organizations_subscription_response_current_plan.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_subscription_response_payment_method.py +26 -0
- mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change.py +34 -0
- mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change_target_plan.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_update_response.py +4 -3
- mirascope/api/_generated/organizations/types/organizations_update_response_role.py +1 -3
- mirascope/api/_generated/organizations/types/organizations_update_subscription_request_target_plan.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_update_subscription_response.py +35 -0
- mirascope/api/_generated/project_memberships/__init__.py +25 -0
- mirascope/api/_generated/project_memberships/client.py +437 -0
- mirascope/api/_generated/project_memberships/raw_client.py +1039 -0
- mirascope/api/_generated/project_memberships/types/__init__.py +29 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_create_request_role.py +7 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_create_response.py +35 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_create_response_role.py +7 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item.py +33 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item_role.py +7 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_update_request_role.py +7 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_update_response.py +35 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_update_response_role.py +7 -0
- mirascope/api/_generated/projects/__init__.py +2 -12
- mirascope/api/_generated/projects/client.py +17 -71
- mirascope/api/_generated/projects/raw_client.py +295 -51
- mirascope/api/_generated/projects/types/__init__.py +1 -6
- mirascope/api/_generated/projects/types/projects_create_response.py +3 -9
- mirascope/api/_generated/projects/types/projects_get_response.py +3 -9
- mirascope/api/_generated/projects/types/projects_list_response_item.py +3 -9
- mirascope/api/_generated/projects/types/projects_update_response.py +3 -9
- mirascope/api/_generated/reference.md +3619 -182
- mirascope/api/_generated/tags/__init__.py +19 -0
- mirascope/api/_generated/tags/client.py +504 -0
- mirascope/api/_generated/tags/raw_client.py +1288 -0
- mirascope/api/_generated/tags/types/__init__.py +17 -0
- mirascope/api/_generated/tags/types/tags_create_response.py +41 -0
- mirascope/api/_generated/tags/types/tags_get_response.py +41 -0
- mirascope/api/_generated/tags/types/tags_list_response.py +23 -0
- mirascope/api/_generated/tags/types/tags_list_response_tags_item.py +41 -0
- mirascope/api/_generated/tags/types/tags_update_response.py +41 -0
- mirascope/api/_generated/token_cost/__init__.py +7 -0
- mirascope/api/_generated/token_cost/client.py +160 -0
- mirascope/api/_generated/token_cost/raw_client.py +264 -0
- mirascope/api/_generated/token_cost/types/__init__.py +8 -0
- mirascope/api/_generated/token_cost/types/token_cost_calculate_request_usage.py +54 -0
- mirascope/api/_generated/token_cost/types/token_cost_calculate_response.py +52 -0
- mirascope/api/_generated/traces/__init__.py +42 -0
- mirascope/api/_generated/traces/client.py +941 -0
- mirascope/api/_generated/traces/raw_client.py +2177 -23
- mirascope/api/_generated/traces/types/__init__.py +60 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +4 -11
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +1 -3
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +8 -24
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +3 -9
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +3 -9
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +4 -8
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +8 -24
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +3 -9
- 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
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +6 -18
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +3 -9
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +8 -24
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +2 -6
- 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
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_response.py +2 -5
- mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +3 -9
- mirascope/api/_generated/traces/types/traces_get_analytics_summary_response.py +60 -0
- mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_functions_item.py +24 -0
- mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_models_item.py +22 -0
- mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response.py +33 -0
- mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response_spans_item.py +88 -0
- mirascope/api/_generated/traces/types/traces_get_trace_detail_response.py +33 -0
- mirascope/api/_generated/traces/types/traces_get_trace_detail_response_spans_item.py +88 -0
- mirascope/api/_generated/traces/types/traces_list_by_function_hash_response.py +25 -0
- mirascope/api/_generated/traces/types/traces_list_by_function_hash_response_traces_item.py +44 -0
- mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item.py +26 -0
- mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item_operator.py +7 -0
- mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_by.py +7 -0
- mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_order.py +7 -0
- mirascope/api/_generated/traces/types/traces_search_by_env_response.py +26 -0
- mirascope/api/_generated/traces/types/traces_search_by_env_response_spans_item.py +50 -0
- mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item.py +26 -0
- mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item_operator.py +7 -0
- mirascope/api/_generated/traces/types/traces_search_request_sort_by.py +7 -0
- mirascope/api/_generated/traces/types/traces_search_request_sort_order.py +5 -0
- mirascope/api/_generated/traces/types/traces_search_response.py +26 -0
- mirascope/api/_generated/traces/types/traces_search_response_spans_item.py +50 -0
- mirascope/api/_generated/types/__init__.py +48 -0
- mirascope/api/_generated/types/already_exists_error.py +1 -3
- mirascope/api/_generated/types/bad_request_error_body.py +50 -0
- mirascope/api/_generated/types/click_house_error.py +22 -0
- mirascope/api/_generated/types/database_error.py +1 -3
- mirascope/api/_generated/types/date.py +3 -0
- mirascope/api/_generated/types/http_api_decode_error.py +1 -3
- mirascope/api/_generated/types/immutable_resource_error.py +22 -0
- mirascope/api/_generated/types/internal_server_error_body.py +49 -0
- mirascope/api/_generated/types/issue.py +1 -3
- mirascope/api/_generated/types/issue_tag.py +1 -8
- mirascope/api/_generated/types/not_found_error_body.py +1 -3
- mirascope/api/_generated/types/number_from_string.py +3 -0
- mirascope/api/_generated/types/permission_denied_error.py +1 -3
- mirascope/api/_generated/types/permission_denied_error_tag.py +1 -3
- mirascope/api/_generated/types/plan_limit_exceeded_error.py +32 -0
- mirascope/api/_generated/types/plan_limit_exceeded_error_tag.py +7 -0
- mirascope/api/_generated/types/pricing_unavailable_error.py +23 -0
- mirascope/api/_generated/types/property_key_key.py +1 -3
- mirascope/api/_generated/types/rate_limit_error.py +31 -0
- mirascope/api/_generated/types/rate_limit_error_tag.py +5 -0
- mirascope/api/_generated/types/service_unavailable_error_body.py +24 -0
- mirascope/api/_generated/types/service_unavailable_error_tag.py +7 -0
- mirascope/api/_generated/types/stripe_error.py +20 -0
- mirascope/api/_generated/types/subscription_past_due_error.py +31 -0
- mirascope/api/_generated/types/subscription_past_due_error_tag.py +7 -0
- mirascope/api/_generated/types/unauthorized_error_body.py +21 -0
- mirascope/api/_generated/types/unauthorized_error_tag.py +5 -0
- mirascope/api/settings.py +19 -1
- mirascope/llm/__init__.py +55 -8
- mirascope/llm/calls/__init__.py +2 -1
- mirascope/llm/calls/calls.py +3 -1
- mirascope/llm/calls/decorator.py +21 -7
- mirascope/llm/content/tool_call.py +6 -0
- mirascope/llm/content/tool_output.py +22 -5
- mirascope/llm/exceptions.py +284 -71
- mirascope/llm/formatting/__init__.py +19 -2
- mirascope/llm/formatting/format.py +219 -30
- mirascope/llm/formatting/output_parser.py +178 -0
- mirascope/llm/formatting/partial.py +80 -7
- mirascope/llm/formatting/primitives.py +192 -0
- mirascope/llm/formatting/types.py +21 -64
- mirascope/llm/mcp/__init__.py +2 -2
- mirascope/llm/mcp/mcp_client.py +130 -0
- mirascope/llm/messages/__init__.py +3 -0
- mirascope/llm/messages/_utils.py +34 -0
- mirascope/llm/models/__init__.py +5 -0
- mirascope/llm/models/models.py +137 -69
- mirascope/llm/{providers/base → models}/params.py +16 -37
- mirascope/llm/models/thinking_config.py +61 -0
- mirascope/llm/prompts/_utils.py +0 -32
- mirascope/llm/prompts/decorator.py +16 -5
- mirascope/llm/prompts/prompts.py +131 -68
- mirascope/llm/providers/__init__.py +18 -2
- mirascope/llm/providers/anthropic/__init__.py +3 -21
- mirascope/llm/providers/anthropic/_utils/__init__.py +2 -0
- mirascope/llm/providers/anthropic/_utils/beta_decode.py +22 -11
- mirascope/llm/providers/anthropic/_utils/beta_encode.py +75 -25
- mirascope/llm/providers/anthropic/_utils/decode.py +22 -11
- mirascope/llm/providers/anthropic/_utils/encode.py +82 -20
- mirascope/llm/providers/anthropic/_utils/errors.py +2 -2
- mirascope/llm/providers/anthropic/beta_provider.py +64 -18
- mirascope/llm/providers/anthropic/provider.py +91 -33
- mirascope/llm/providers/base/__init__.py +0 -2
- mirascope/llm/providers/base/_utils.py +55 -11
- mirascope/llm/providers/base/base_provider.py +116 -37
- mirascope/llm/providers/google/__init__.py +2 -17
- mirascope/llm/providers/google/_utils/__init__.py +2 -0
- mirascope/llm/providers/google/_utils/decode.py +37 -15
- mirascope/llm/providers/google/_utils/encode.py +127 -19
- mirascope/llm/providers/google/_utils/errors.py +3 -2
- mirascope/llm/providers/google/model_info.py +1 -0
- mirascope/llm/providers/google/provider.py +68 -19
- mirascope/llm/providers/mirascope/__init__.py +5 -0
- mirascope/llm/providers/mirascope/_utils.py +73 -0
- mirascope/llm/providers/mirascope/provider.py +349 -0
- mirascope/llm/providers/mlx/__init__.py +2 -17
- mirascope/llm/providers/mlx/_utils.py +8 -3
- mirascope/llm/providers/mlx/encoding/base.py +5 -2
- mirascope/llm/providers/mlx/encoding/transformers.py +5 -2
- mirascope/llm/providers/mlx/mlx.py +23 -6
- mirascope/llm/providers/mlx/provider.py +42 -13
- mirascope/llm/providers/ollama/__init__.py +1 -13
- mirascope/llm/providers/openai/_utils/errors.py +2 -2
- mirascope/llm/providers/openai/completions/__init__.py +2 -20
- mirascope/llm/providers/openai/completions/_utils/decode.py +14 -3
- mirascope/llm/providers/openai/completions/_utils/encode.py +35 -28
- mirascope/llm/providers/openai/completions/base_provider.py +40 -11
- mirascope/llm/providers/openai/provider.py +40 -10
- mirascope/llm/providers/openai/responses/__init__.py +1 -17
- mirascope/llm/providers/openai/responses/_utils/__init__.py +2 -0
- mirascope/llm/providers/openai/responses/_utils/decode.py +21 -8
- mirascope/llm/providers/openai/responses/_utils/encode.py +59 -19
- mirascope/llm/providers/openai/responses/provider.py +56 -18
- mirascope/llm/providers/provider_id.py +1 -0
- mirascope/llm/providers/provider_registry.py +96 -19
- mirascope/llm/providers/together/__init__.py +1 -13
- mirascope/llm/responses/__init__.py +6 -1
- mirascope/llm/responses/_utils.py +102 -12
- mirascope/llm/responses/base_response.py +5 -2
- mirascope/llm/responses/base_stream_response.py +139 -45
- mirascope/llm/responses/response.py +2 -1
- mirascope/llm/responses/root_response.py +89 -17
- mirascope/llm/responses/stream_response.py +6 -9
- mirascope/llm/tools/decorator.py +17 -8
- mirascope/llm/tools/tool_schema.py +43 -10
- mirascope/llm/tools/toolkit.py +35 -27
- mirascope/llm/tools/tools.py +123 -30
- mirascope/ops/__init__.py +64 -109
- mirascope/ops/_internal/configuration.py +82 -31
- mirascope/ops/_internal/exporters/exporters.py +64 -11
- mirascope/ops/_internal/instrumentation/llm/common.py +530 -0
- mirascope/ops/_internal/instrumentation/llm/cost.py +190 -0
- mirascope/ops/_internal/instrumentation/llm/encode.py +1 -1
- mirascope/ops/_internal/instrumentation/llm/llm.py +116 -1243
- mirascope/ops/_internal/instrumentation/llm/model.py +1798 -0
- mirascope/ops/_internal/instrumentation/llm/response.py +521 -0
- mirascope/ops/_internal/instrumentation/llm/serialize.py +300 -0
- mirascope/ops/_internal/protocols.py +83 -1
- mirascope/ops/_internal/traced_calls.py +4 -0
- mirascope/ops/_internal/traced_functions.py +141 -12
- mirascope/ops/_internal/tracing.py +78 -1
- mirascope/ops/_internal/utils.py +52 -4
- mirascope/ops/_internal/versioned_functions.py +54 -43
- {mirascope-2.0.0a5.dist-info → mirascope-2.0.1.dist-info}/METADATA +14 -13
- mirascope-2.0.1.dist-info/RECORD +423 -0
- {mirascope-2.0.0a5.dist-info → mirascope-2.0.1.dist-info}/licenses/LICENSE +1 -1
- mirascope/llm/formatting/_utils.py +0 -78
- mirascope/llm/mcp/client.py +0 -118
- mirascope/llm/providers/_missing_import_stubs.py +0 -49
- mirascope-2.0.0a5.dist-info/RECORD +0 -265
- {mirascope-2.0.0a5.dist-info → mirascope-2.0.1.dist-info}/WHEEL +0 -0
|
@@ -1,20 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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{...
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 ..
|
|
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
|
|
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 ..
|
|
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 |
|
|
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
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
|
352
|
+
f"Received tool_call_chunk for unknown tool call ID: {chunk.id}"
|
|
339
353
|
)
|
|
340
|
-
|
|
354
|
+
tool_call.args += chunk.delta
|
|
341
355
|
|
|
342
356
|
elif chunk.type == "tool_call_end_chunk":
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
|
361
|
+
f"Received tool_call_end_chunk for unknown tool call ID: {chunk.id}"
|
|
349
362
|
)
|
|
350
|
-
if not
|
|
351
|
-
|
|
352
|
-
self._content.append(
|
|
353
|
-
self._tool_calls.append(
|
|
354
|
-
self.
|
|
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
|
-
"""
|
|
562
|
+
"""Drive the stream forward, yielding partial formatted outputs as they arrive.
|
|
533
563
|
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
-
|
|
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
|
-
|
|
708
|
-
yield "**[No Content]**"
|
|
709
|
-
|
|
710
|
-
def structured_stream(
|
|
781
|
+
async def structured_stream(
|
|
711
782
|
self,
|
|
712
783
|
) -> AsyncIterator[Partial[FormattableT]]:
|
|
713
|
-
"""
|
|
784
|
+
"""Drive the stream forward, yielding partial formatted outputs as they arrive.
|
|
714
785
|
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
|
|
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 ..
|
|
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 ..
|
|
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,
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
129
|
-
raise NotImplementedError
|
|
168
|
+
text = self.text("")
|
|
130
169
|
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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 ..
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
493
|
+
async for chunk in stream_response.text_stream():
|
|
496
494
|
print(chunk, end="", flush=True)
|
|
497
|
-
print()
|
|
498
495
|
```
|
|
499
496
|
"""
|
|
500
497
|
|