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,32 +1,173 @@
|
|
|
1
1
|
"""The `llm.format` decorator for defining response formats as classes."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import inspect
|
|
6
|
+
import json
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Generic, cast
|
|
4
9
|
|
|
10
|
+
from ..tools import FORMAT_TOOL_NAME, ToolFn, ToolParameterSchema, ToolSchema
|
|
5
11
|
from ..types import NoneType
|
|
6
|
-
from .
|
|
7
|
-
from .
|
|
12
|
+
from .output_parser import OutputParser, is_output_parser
|
|
13
|
+
from .primitives import create_wrapper_model, is_primitive_type
|
|
14
|
+
from .types import FormattableT, FormattingMode, HasFormattingInstructions
|
|
15
|
+
|
|
16
|
+
TOOL_MODE_INSTRUCTIONS = f"""Always respond to the user's query using the {FORMAT_TOOL_NAME} tool for structured output."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
JSON_MODE_INSTRUCTIONS = (
|
|
20
|
+
"Respond only with valid JSON that matches this exact schema:\n{json_schema}"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(kw_only=True)
|
|
25
|
+
class Format(Generic[FormattableT]):
|
|
26
|
+
"""Class representing a structured output format for LLM responses.
|
|
27
|
+
|
|
28
|
+
A `Format` contains metadata needed to describe a structured output type
|
|
29
|
+
to the LLM, including the expected schema. This class is not instantiated directly,
|
|
30
|
+
but is created by calling `llm.format`, or is automatically generated by LLM
|
|
31
|
+
providers when a `Formattable` is passed to a call method.
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from mirascope import llm
|
|
37
|
+
|
|
38
|
+
class Book:
|
|
39
|
+
title: str
|
|
40
|
+
author: str
|
|
41
|
+
|
|
42
|
+
print(llm.format(Book, mode="tool"))
|
|
43
|
+
```
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
name: str
|
|
47
|
+
"""The name of the response format."""
|
|
48
|
+
|
|
49
|
+
description: str | None
|
|
50
|
+
"""A description of the response format, if available."""
|
|
51
|
+
|
|
52
|
+
schema: dict[str, object]
|
|
53
|
+
"""JSON schema representation of the structured output format."""
|
|
54
|
+
|
|
55
|
+
mode: FormattingMode
|
|
56
|
+
"""The decorator-provided mode of the response format.
|
|
57
|
+
|
|
58
|
+
Determines how the LLM call may be modified in order to extract the expected format.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
formattable: type[FormattableT] | OutputParser[FormattableT]
|
|
62
|
+
"""The formattable type or custom output parser.
|
|
63
|
+
|
|
64
|
+
Can be one of:
|
|
65
|
+
- type[BaseModel]: A Pydantic model class for structured output
|
|
66
|
+
- PrimitiveType: A primitive type (str, int, list, etc.) for simple output
|
|
67
|
+
- OutputParser[FormattableT]: A custom parser created with @llm.output_parser
|
|
68
|
+
|
|
69
|
+
The type determines how the response will be parsed in response.parse().
|
|
70
|
+
OutputParser uses Any for the response type since it works with any response.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def formatting_instructions(self) -> str | None:
|
|
75
|
+
"""The formatting instructions that will be added to the LLM system prompt.
|
|
76
|
+
|
|
77
|
+
If the format has a custom `OutputParser`, its formatting instructions will be used.
|
|
78
|
+
Otherwise, if the format type has a `formatting_instructions` class method,
|
|
79
|
+
the output of that call will be used. Otherwise, instructions may be
|
|
80
|
+
auto-generated based on the formatting mode.
|
|
81
|
+
"""
|
|
82
|
+
if is_output_parser(self.formattable) or isinstance(
|
|
83
|
+
self.formattable, HasFormattingInstructions
|
|
84
|
+
):
|
|
85
|
+
return self.formattable.formatting_instructions()
|
|
86
|
+
|
|
87
|
+
if self.mode == "tool":
|
|
88
|
+
return TOOL_MODE_INSTRUCTIONS
|
|
89
|
+
elif self.mode == "json":
|
|
90
|
+
json_schema = json.dumps(self.schema, indent=2)
|
|
91
|
+
instructions = JSON_MODE_INSTRUCTIONS.format(json_schema=json_schema)
|
|
92
|
+
return inspect.cleandoc(instructions)
|
|
93
|
+
elif self.mode == "parser":
|
|
94
|
+
return None # pragma: no cover
|
|
95
|
+
|
|
96
|
+
def create_tool_schema(
|
|
97
|
+
self,
|
|
98
|
+
) -> ToolSchema[ToolFn[..., None]]:
|
|
99
|
+
"""Generate a `ToolSchema` for parsing this format.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
`ToolSchema` for the format tool
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
schema_dict: dict[str, Any] = self.schema.copy()
|
|
106
|
+
schema_dict["type"] = "object"
|
|
107
|
+
|
|
108
|
+
properties = schema_dict.get("properties")
|
|
109
|
+
if not properties or not isinstance(properties, dict):
|
|
110
|
+
properties = {} # pragma: no cover
|
|
111
|
+
properties = cast(dict[str, Any], properties)
|
|
112
|
+
required: list[str] = list(properties.keys())
|
|
113
|
+
|
|
114
|
+
description = (
|
|
115
|
+
f"Use this tool to extract data in {self.name} format for a final response."
|
|
116
|
+
)
|
|
117
|
+
if self.description:
|
|
118
|
+
description += "\n" + self.description
|
|
119
|
+
|
|
120
|
+
parameters = ToolParameterSchema(
|
|
121
|
+
properties=properties,
|
|
122
|
+
required=required,
|
|
123
|
+
additionalProperties=False,
|
|
124
|
+
)
|
|
125
|
+
if "$defs" in schema_dict and isinstance(schema_dict["$defs"], dict):
|
|
126
|
+
parameters.defs = schema_dict["$defs"]
|
|
127
|
+
|
|
128
|
+
def _unused_format_fn() -> None:
|
|
129
|
+
raise TypeError(
|
|
130
|
+
"Format tool function should not be called."
|
|
131
|
+
) # pragma: no cover
|
|
132
|
+
|
|
133
|
+
return ToolSchema(
|
|
134
|
+
fn=cast(ToolFn[..., None], _unused_format_fn),
|
|
135
|
+
name=FORMAT_TOOL_NAME,
|
|
136
|
+
description=description,
|
|
137
|
+
parameters=parameters,
|
|
138
|
+
strict=None, # Provider determines whether to use strict mode.
|
|
139
|
+
)
|
|
8
140
|
|
|
9
141
|
|
|
10
142
|
def format(
|
|
11
|
-
formattable: type[FormattableT] | None,
|
|
143
|
+
formattable: type[FormattableT] | OutputParser[FormattableT] | None,
|
|
12
144
|
*,
|
|
13
145
|
mode: FormattingMode,
|
|
14
146
|
) -> Format[FormattableT] | None:
|
|
15
|
-
"""Returns a `Format` that describes structured output
|
|
147
|
+
"""Returns a `Format` that describes structured output or custom parsing.
|
|
148
|
+
|
|
149
|
+
This function converts a Formattable type (e.g. Pydantic `BaseModel` or primitive type)
|
|
150
|
+
or an `OutputParser` into a `Format` object that describes how the output should be
|
|
151
|
+
formatted and parsed. Calling `llm.format` is optional, as all the APIs that expect
|
|
152
|
+
a `Format` can also take the Formattable type or `OutputParser` directly. However,
|
|
153
|
+
calling `llm.format` is necessary in order to specify the formatting mode for
|
|
154
|
+
`BaseModel`/primitive types.
|
|
16
155
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
is optional, as all the APIs that expect a `Format` can also take the Formattable
|
|
20
|
-
type directly. However, calling `llm.format` is necessary in order to specify the
|
|
21
|
-
formatting mode that will be used.
|
|
156
|
+
Primitive types are automatically wrapped in a `BaseModel` with an "output" field
|
|
157
|
+
for schema generation, then unwrapped during parsing.
|
|
22
158
|
|
|
23
159
|
Args:
|
|
24
|
-
|
|
160
|
+
formattable: The type or parser to format:
|
|
161
|
+
- BaseModel type: Uses structured output with JSON schema
|
|
162
|
+
- Primitive type: Wrapped in schema for structured output
|
|
163
|
+
- OutputParser: Uses custom parsing with instructions
|
|
164
|
+
mode: The format mode to use (required):
|
|
25
165
|
- "strict": Use model strict structured outputs, or fail if unavailable.
|
|
26
166
|
- "tool": Use forced tool calling with a special tool that represents a
|
|
27
167
|
formatted response.
|
|
28
168
|
- "json": Use provider json mode if available, or modify prompt to request
|
|
29
169
|
json if not.
|
|
170
|
+
- "parser": Must be used for OutputParser types.
|
|
30
171
|
|
|
31
172
|
The Formattable type may provide custom formatting instructions via a
|
|
32
173
|
`formatting_instructions(cls)` classmethod. If that method is present, it will be called,
|
|
@@ -37,34 +178,44 @@ def format(
|
|
|
37
178
|
you can add the `formatting_instructions` classmethod and have it return `None`.
|
|
38
179
|
|
|
39
180
|
Returns:
|
|
40
|
-
A `Format` object describing the
|
|
181
|
+
A `Format` object describing the format type or parser.
|
|
41
182
|
|
|
42
183
|
Example:
|
|
43
|
-
Using with
|
|
184
|
+
Using with a BaseModel:
|
|
44
185
|
|
|
45
186
|
```python
|
|
46
187
|
from pydantic import BaseModel
|
|
47
|
-
|
|
48
188
|
from mirascope import llm
|
|
49
189
|
|
|
50
|
-
|
|
51
190
|
class Book(BaseModel):
|
|
52
191
|
title: str
|
|
53
192
|
author: str
|
|
54
193
|
|
|
55
194
|
format = llm.format(Book, mode="strict")
|
|
56
195
|
|
|
57
|
-
@llm.call(
|
|
58
|
-
provider_id="openai",
|
|
59
|
-
model_id="openai/gpt-5-mini",
|
|
60
|
-
format=format,
|
|
61
|
-
)
|
|
196
|
+
@llm.call("openai/gpt-5-mini", format=format)
|
|
62
197
|
def recommend_book(genre: str):
|
|
63
198
|
return f"Recommend a {genre} book."
|
|
64
199
|
|
|
65
200
|
response = recommend_book("fantasy")
|
|
66
201
|
book: Book = response.parse()
|
|
67
|
-
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Example:
|
|
205
|
+
|
|
206
|
+
Using with an `OutputParser`:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
@llm.output_parser(
|
|
210
|
+
formatting_instructions="Return XML: <book><title>...</title></book>"
|
|
211
|
+
)
|
|
212
|
+
def parse_book_xml(response: llm.AnyResponse) -> Book:
|
|
213
|
+
# ... parsing logic ...
|
|
214
|
+
return Book(...)
|
|
215
|
+
|
|
216
|
+
@llm.call("openai/gpt-5-mini", format=parse_book_xml)
|
|
217
|
+
def recommend_book(genre: str):
|
|
218
|
+
return f"Recommend a {genre} book."
|
|
68
219
|
```
|
|
69
220
|
"""
|
|
70
221
|
# TODO: Add caching or memoization to this function (e.g. functools.lru_cache)
|
|
@@ -72,33 +223,71 @@ def format(
|
|
|
72
223
|
if formattable is None or formattable is NoneType:
|
|
73
224
|
return None
|
|
74
225
|
|
|
226
|
+
if is_output_parser(formattable):
|
|
227
|
+
if mode != "parser":
|
|
228
|
+
raise ValueError(f"mode must be 'parser' for OutputParser, got '{mode}'")
|
|
229
|
+
return Format[Any](
|
|
230
|
+
name=formattable.__name__,
|
|
231
|
+
description=formattable.__doc__,
|
|
232
|
+
schema={},
|
|
233
|
+
mode="parser",
|
|
234
|
+
formattable=formattable,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if is_primitive_type(formattable):
|
|
238
|
+
wrapper_model = create_wrapper_model(formattable)
|
|
239
|
+
schema = wrapper_model.model_json_schema()
|
|
240
|
+
name = (
|
|
241
|
+
formattable.__name__
|
|
242
|
+
if hasattr(formattable, "__name__")
|
|
243
|
+
else str(formattable)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
return Format[FormattableT](
|
|
247
|
+
name=name,
|
|
248
|
+
description=None,
|
|
249
|
+
schema=schema,
|
|
250
|
+
mode=mode,
|
|
251
|
+
formattable=formattable,
|
|
252
|
+
)
|
|
253
|
+
|
|
75
254
|
description = None
|
|
76
255
|
if formattable.__doc__:
|
|
77
256
|
description = inspect.cleandoc(formattable.__doc__)
|
|
78
257
|
|
|
79
258
|
schema = formattable.model_json_schema()
|
|
80
|
-
formatting_instructions = None
|
|
81
|
-
if isinstance(formattable, HasFormattingInstructions):
|
|
82
|
-
formatting_instructions = formattable.formatting_instructions()
|
|
83
|
-
else:
|
|
84
|
-
formatting_instructions = default_formatting_instructions(schema, mode)
|
|
85
259
|
|
|
86
260
|
return Format[FormattableT](
|
|
87
261
|
name=formattable.__name__,
|
|
88
262
|
description=description,
|
|
89
263
|
schema=schema,
|
|
90
264
|
mode=mode,
|
|
91
|
-
formatting_instructions=formatting_instructions,
|
|
92
265
|
formattable=formattable,
|
|
93
266
|
)
|
|
94
267
|
|
|
95
268
|
|
|
96
269
|
def resolve_format(
|
|
97
|
-
formattable:
|
|
270
|
+
formattable: (
|
|
271
|
+
type[FormattableT] | Format[FormattableT] | OutputParser[FormattableT] | None
|
|
272
|
+
),
|
|
98
273
|
default_mode: FormattingMode,
|
|
99
274
|
) -> Format[FormattableT] | None:
|
|
100
|
-
"""Resolve a `Format` (or None) from a possible `Format
|
|
275
|
+
"""Resolve a `Format` (or None) from a possible `Format`, Formattable, or `OutputParser`.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
formattable: The format specification:
|
|
279
|
+
- Format: Returned as-is
|
|
280
|
+
- BaseModel/primitive type: Converted to Format with default_mode
|
|
281
|
+
- OutputParser: Converted to Format with mode='parser'
|
|
282
|
+
default_mode: The mode to use for BaseModel/primitive types.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
A Format object or None.
|
|
286
|
+
"""
|
|
101
287
|
if isinstance(formattable, Format):
|
|
102
288
|
return formattable
|
|
103
|
-
|
|
104
|
-
|
|
289
|
+
|
|
290
|
+
if is_output_parser(formattable):
|
|
291
|
+
return format(formattable, mode="parser")
|
|
292
|
+
|
|
293
|
+
return format(formattable, mode=default_mode)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""The `llm.output_parser` decorator for creating custom output parsers."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
5
|
+
from typing_extensions import TypeIs
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ..responses import AnyResponse
|
|
9
|
+
|
|
10
|
+
OutputT = TypeVar("OutputT")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OutputParser(Generic[OutputT]):
|
|
14
|
+
"""Represents a custom output parser created with @llm.output_parser.
|
|
15
|
+
|
|
16
|
+
This class wraps a parsing function and stores formatting instructions.
|
|
17
|
+
It is created by the @llm.output_parser decorator and used as a format
|
|
18
|
+
argument in LLM calls.
|
|
19
|
+
|
|
20
|
+
Unlike BaseModel and primitive type formats that use structured outputs
|
|
21
|
+
(JSON schema, tools, strict mode), OutputParser works with raw text responses
|
|
22
|
+
and custom parsing logic.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
```python
|
|
26
|
+
@llm.output_parser(
|
|
27
|
+
formatting_instructions="Return XML: <book><title>...</title></book>"
|
|
28
|
+
)
|
|
29
|
+
def parse_book_xml(response: llm.AnyResponse) -> Book:
|
|
30
|
+
text = "".join(part.text for part in response.texts)
|
|
31
|
+
root = ET.fromstring(text)
|
|
32
|
+
return Book(title=root.find("title").text, ...)
|
|
33
|
+
|
|
34
|
+
@llm.call("openai/gpt-4o", format=parse_book_xml)
|
|
35
|
+
def recommend_book(genre: str):
|
|
36
|
+
return f"Recommend a {genre} book."
|
|
37
|
+
|
|
38
|
+
response = recommend_book("fantasy")
|
|
39
|
+
book = response.parse() # Returns Book instance
|
|
40
|
+
```
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
func: Callable[["AnyResponse"], OutputT],
|
|
46
|
+
formatting_instructions: str,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Initialize the OutputParser.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
func: The parsing function that takes a Response and returns parsed output.
|
|
52
|
+
formatting_instructions: Instructions for the LLM on how to format output.
|
|
53
|
+
"""
|
|
54
|
+
self.func = func
|
|
55
|
+
self._formatting_instructions = formatting_instructions
|
|
56
|
+
self.__name__ = func.__name__
|
|
57
|
+
self.__doc__ = func.__doc__
|
|
58
|
+
|
|
59
|
+
def formatting_instructions(self) -> str:
|
|
60
|
+
"""Return the formatting instructions for the LLM.
|
|
61
|
+
|
|
62
|
+
These instructions are added to the system prompt to guide the LLM
|
|
63
|
+
on how to format its output for parsing.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
The formatting instructions string.
|
|
67
|
+
"""
|
|
68
|
+
return self._formatting_instructions
|
|
69
|
+
|
|
70
|
+
def __call__(self, response: "AnyResponse") -> OutputT:
|
|
71
|
+
"""Parse the response using the wrapped function.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
response: The response object from the LLM call.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
The parsed output of type OutputT.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
Any exception raised by the wrapped parsing function.
|
|
81
|
+
"""
|
|
82
|
+
return self.func(response)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def output_parser(
|
|
86
|
+
*,
|
|
87
|
+
formatting_instructions: str,
|
|
88
|
+
) -> Callable[[Callable[["AnyResponse"], OutputT]], OutputParser[OutputT]]:
|
|
89
|
+
"""Decorator to create an output parser for custom format parsing.
|
|
90
|
+
|
|
91
|
+
Use this decorator to create custom parsers for non-JSON formats like
|
|
92
|
+
XML, YAML, CSV, or any custom text structure. The decorated function
|
|
93
|
+
receives the full Response object and returns the parsed output.
|
|
94
|
+
|
|
95
|
+
This is the recommended way to handle custom output formats that don't
|
|
96
|
+
fit the JSON/BaseModel paradigm. The formatting instructions guide the
|
|
97
|
+
LLM on how to structure its output, and the parsing function extracts
|
|
98
|
+
the data you need.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
formatting_instructions: Instructions for the LLM on how to format
|
|
102
|
+
the output. These will be added to the system prompt.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Decorator that converts a function into an OutputParser.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
|
|
109
|
+
XML parsing:
|
|
110
|
+
```python
|
|
111
|
+
@llm.output_parser(
|
|
112
|
+
formatting_instructions='''
|
|
113
|
+
Return the book information in this XML structure:
|
|
114
|
+
<book>
|
|
115
|
+
<title>Book Title</title>
|
|
116
|
+
<author>Author Name</author>
|
|
117
|
+
<rating>5</rating>
|
|
118
|
+
</book>
|
|
119
|
+
'''
|
|
120
|
+
)
|
|
121
|
+
def parse_book_xml(response: llm.AnyResponse) -> Book:
|
|
122
|
+
import xml.etree.ElementTree as ET
|
|
123
|
+
text = "".join(part.text for part in response.texts)
|
|
124
|
+
root = ET.fromstring(text)
|
|
125
|
+
return Book(
|
|
126
|
+
title=root.find("title").text,
|
|
127
|
+
author=root.find("author").text,
|
|
128
|
+
rating=int(root.find("rating").text),
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
|
|
134
|
+
CSV parsing:
|
|
135
|
+
```python
|
|
136
|
+
@llm.output_parser(
|
|
137
|
+
formatting_instructions='''
|
|
138
|
+
Return book information as CSV format with header:
|
|
139
|
+
title,author,rating
|
|
140
|
+
Book 1,Author 1,5
|
|
141
|
+
Book 2,Author 2,4
|
|
142
|
+
'''
|
|
143
|
+
)
|
|
144
|
+
def parse_books_csv(response: llm.AnyResponse) -> list[Book]:
|
|
145
|
+
text = "".join(part.text for part in response.texts)
|
|
146
|
+
lines = text.strip().split('\\n')[1:] # Skip header
|
|
147
|
+
return [
|
|
148
|
+
Book(
|
|
149
|
+
title=line.split(',')[0].strip(),
|
|
150
|
+
author=line.split(',')[1].strip(),
|
|
151
|
+
rating=int(line.split(',')[2]),
|
|
152
|
+
)
|
|
153
|
+
for line in lines
|
|
154
|
+
]
|
|
155
|
+
```
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def decorator(
|
|
159
|
+
func: Callable[["AnyResponse"], OutputT],
|
|
160
|
+
) -> OutputParser[OutputT]:
|
|
161
|
+
return OutputParser(func, formatting_instructions)
|
|
162
|
+
|
|
163
|
+
return decorator
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def is_output_parser(obj: Any) -> TypeIs[OutputParser[Any]]: # noqa: ANN401
|
|
167
|
+
"""Check if an object is an OutputParser.
|
|
168
|
+
|
|
169
|
+
This is a type guard function that narrows the type of `obj` to
|
|
170
|
+
`OutputParser[Any, Any]` when it returns True.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
obj: The object to check.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
True if the object is an OutputParser instance, False otherwise.
|
|
177
|
+
"""
|
|
178
|
+
return isinstance(obj, OutputParser)
|
|
@@ -8,10 +8,16 @@ serves as an acknowledgment of the original author's contribution to this projec
|
|
|
8
8
|
--------------------------------------------------------------------------------
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import inspect
|
|
12
|
+
from typing import Any, Generic, NoReturn, Union, cast, get_args, get_origin
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, create_model
|
|
12
15
|
|
|
13
16
|
from .format import FormattableT
|
|
14
17
|
|
|
18
|
+
# Cache for generated partial models to avoid recreation
|
|
19
|
+
_partial_model_cache: dict[type[Any], type[Any]] = {}
|
|
20
|
+
|
|
15
21
|
|
|
16
22
|
class Partial(Generic[FormattableT]):
|
|
17
23
|
"""Generate a new class with all attributes optionals.
|
|
@@ -34,7 +40,9 @@ class Partial(Generic[FormattableT]):
|
|
|
34
40
|
Raises:
|
|
35
41
|
TypeError: Direct instantiation not allowed.
|
|
36
42
|
"""
|
|
37
|
-
raise TypeError(
|
|
43
|
+
raise TypeError(
|
|
44
|
+
"Cannot instantiate abstract Partial class."
|
|
45
|
+
) # pragma: no cover
|
|
38
46
|
|
|
39
47
|
def __init_subclass__(
|
|
40
48
|
cls,
|
|
@@ -46,13 +54,78 @@ class Partial(Generic[FormattableT]):
|
|
|
46
54
|
Raises:
|
|
47
55
|
TypeError: Subclassing not allowed.
|
|
48
56
|
"""
|
|
49
|
-
raise TypeError(f"Cannot subclass {cls.__module__}.Partial")
|
|
57
|
+
raise TypeError(f"Cannot subclass {cls.__module__}.Partial") # pragma: no cover
|
|
50
58
|
|
|
51
59
|
def __class_getitem__(
|
|
52
60
|
cls,
|
|
53
61
|
wrapped_class: type[FormattableT],
|
|
54
62
|
) -> type[FormattableT]:
|
|
55
|
-
"""Convert model to a partial model with all fields being optionals.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
"""Convert model to a partial model with all fields being optionals.
|
|
64
|
+
|
|
65
|
+
Recursively converts all fields in a Pydantic BaseModel to optional,
|
|
66
|
+
handling nested models and generic types like list[Book].
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
wrapped_class: The BaseModel class to make partial
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
A new BaseModel class with all fields optional (or original if not BaseModel)
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
>>> class Author(BaseModel):
|
|
76
|
+
... first_name: str
|
|
77
|
+
... last_name: str
|
|
78
|
+
>>> class Book(BaseModel):
|
|
79
|
+
... title: str
|
|
80
|
+
... author: Author
|
|
81
|
+
>>> PartialBook = Partial[Book]
|
|
82
|
+
>>> partial = PartialBook(title="The Name")
|
|
83
|
+
>>> partial.author # None
|
|
84
|
+
"""
|
|
85
|
+
# Return non-BaseModel types unchanged
|
|
86
|
+
if not (
|
|
87
|
+
inspect.isclass(wrapped_class) and issubclass(wrapped_class, BaseModel)
|
|
88
|
+
):
|
|
89
|
+
return wrapped_class
|
|
90
|
+
|
|
91
|
+
# Check cache to avoid regenerating
|
|
92
|
+
if wrapped_class in _partial_model_cache:
|
|
93
|
+
return cast(type[FormattableT], _partial_model_cache[wrapped_class])
|
|
94
|
+
|
|
95
|
+
# Recursively make all fields optional
|
|
96
|
+
partial_fields: dict[str, Any] = {}
|
|
97
|
+
for field_name, field_info in wrapped_class.model_fields.items():
|
|
98
|
+
field_type = field_info.annotation
|
|
99
|
+
|
|
100
|
+
# Recursively handle nested BaseModel fields
|
|
101
|
+
if inspect.isclass(field_type) and issubclass(field_type, BaseModel):
|
|
102
|
+
field_type = Partial[field_type]
|
|
103
|
+
|
|
104
|
+
# Handle generic types with BaseModel args (e.g., list[Book], dict[str, Book])
|
|
105
|
+
origin = get_origin(field_type)
|
|
106
|
+
if origin is not None:
|
|
107
|
+
args = get_args(field_type)
|
|
108
|
+
# Recursively convert BaseModel args to partial
|
|
109
|
+
new_args = tuple(
|
|
110
|
+
Partial[arg]
|
|
111
|
+
if inspect.isclass(arg) and issubclass(arg, BaseModel)
|
|
112
|
+
else arg
|
|
113
|
+
for arg in args
|
|
114
|
+
)
|
|
115
|
+
# Reconstruct generic type with new args
|
|
116
|
+
if new_args != args:
|
|
117
|
+
field_type = origin[new_args]
|
|
118
|
+
|
|
119
|
+
# Make field optional with None default
|
|
120
|
+
optional_type = Union[field_type, None] # noqa: UP007
|
|
121
|
+
partial_fields[field_name] = (optional_type, None)
|
|
122
|
+
|
|
123
|
+
# Create new model with "Partial" prefix
|
|
124
|
+
partial_model = create_model(
|
|
125
|
+
f"Partial{wrapped_class.__name__}", __base__=BaseModel, **partial_fields
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Cache the generated model
|
|
129
|
+
_partial_model_cache[wrapped_class] = partial_model
|
|
130
|
+
|
|
131
|
+
return cast(type[FormattableT], partial_model)
|