mirascope 2.0.0a6__py3-none-any.whl → 2.0.2__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/_utils.py +34 -0
- mirascope/api/_generated/__init__.py +186 -5
- mirascope/api/_generated/annotations/client.py +38 -6
- mirascope/api/_generated/annotations/raw_client.py +366 -47
- mirascope/api/_generated/annotations/types/annotations_create_response.py +19 -6
- mirascope/api/_generated/annotations/types/annotations_get_response.py +19 -6
- mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item.py +22 -7
- mirascope/api/_generated/annotations/types/annotations_update_response.py +19 -6
- mirascope/api/_generated/api_keys/__init__.py +12 -2
- mirascope/api/_generated/api_keys/client.py +107 -6
- mirascope/api/_generated/api_keys/raw_client.py +486 -38
- mirascope/api/_generated/api_keys/types/__init__.py +7 -1
- mirascope/api/_generated/api_keys/types/api_keys_list_all_for_org_response_item.py +40 -0
- mirascope/api/_generated/client.py +36 -0
- mirascope/api/_generated/docs/raw_client.py +71 -9
- mirascope/api/_generated/environment.py +3 -3
- mirascope/api/_generated/environments/__init__.py +6 -0
- mirascope/api/_generated/environments/client.py +158 -9
- mirascope/api/_generated/environments/raw_client.py +620 -52
- mirascope/api/_generated/environments/types/__init__.py +10 -0
- 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/{organizations/types/organizations_credits_response.py → environments/types/environments_get_analytics_response_top_models_item.py} +6 -3
- mirascope/api/_generated/errors/__init__.py +6 -0
- mirascope/api/_generated/errors/bad_request_error.py +5 -2
- mirascope/api/_generated/errors/conflict_error.py +5 -2
- 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/functions/__init__.py +10 -0
- mirascope/api/_generated/functions/client.py +222 -8
- mirascope/api/_generated/functions/raw_client.py +975 -134
- mirascope/api/_generated/functions/types/__init__.py +28 -4
- 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_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/health/raw_client.py +74 -10
- 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 -2
- mirascope/api/_generated/organizations/client.py +442 -20
- mirascope/api/_generated/organizations/raw_client.py +1763 -164
- mirascope/api/_generated/organizations/types/__init__.py +48 -2
- mirascope/api/_generated/organizations/types/organizations_create_payment_intent_response.py +24 -0
- 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_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/raw_client.py +415 -58
- mirascope/api/_generated/reference.md +2767 -397
- 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 +20 -0
- mirascope/api/_generated/traces/client.py +543 -0
- mirascope/api/_generated/traces/raw_client.py +1366 -96
- mirascope/api/_generated/traces/types/__init__.py +28 -0
- mirascope/api/_generated/traces/types/traces_get_analytics_summary_response.py +6 -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_spans_item.py +0 -2
- 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_response_spans_item.py +10 -1
- mirascope/api/_generated/types/__init__.py +32 -2
- mirascope/api/_generated/types/bad_request_error_body.py +50 -0
- mirascope/api/_generated/types/date.py +3 -0
- mirascope/api/_generated/types/immutable_resource_error.py +22 -0
- mirascope/api/_generated/types/internal_server_error_body.py +3 -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/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/subscription_past_due_error.py +31 -0
- mirascope/api/_generated/types/subscription_past_due_error_tag.py +7 -0
- mirascope/api/settings.py +19 -1
- mirascope/llm/__init__.py +53 -10
- mirascope/llm/calls/__init__.py +2 -1
- mirascope/llm/calls/calls.py +29 -20
- mirascope/llm/calls/decorator.py +21 -7
- mirascope/llm/content/tool_output.py +22 -5
- mirascope/llm/exceptions.py +284 -71
- mirascope/llm/formatting/__init__.py +17 -0
- mirascope/llm/formatting/format.py +112 -35
- 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 +20 -8
- 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 +7 -57
- 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 +160 -92
- mirascope/llm/providers/__init__.py +1 -4
- mirascope/llm/providers/anthropic/_utils/__init__.py +2 -0
- mirascope/llm/providers/anthropic/_utils/beta_decode.py +18 -9
- mirascope/llm/providers/anthropic/_utils/beta_encode.py +62 -13
- mirascope/llm/providers/anthropic/_utils/decode.py +18 -9
- mirascope/llm/providers/anthropic/_utils/encode.py +26 -7
- 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 -4
- mirascope/llm/providers/base/_utils.py +55 -6
- mirascope/llm/providers/base/base_provider.py +116 -37
- mirascope/llm/providers/google/_utils/__init__.py +2 -0
- mirascope/llm/providers/google/_utils/decode.py +20 -7
- mirascope/llm/providers/google/_utils/encode.py +26 -7
- mirascope/llm/providers/google/_utils/errors.py +3 -2
- mirascope/llm/providers/google/provider.py +64 -18
- mirascope/llm/providers/mirascope/_utils.py +13 -17
- mirascope/llm/providers/mirascope/provider.py +49 -18
- mirascope/llm/providers/mlx/_utils.py +7 -2
- 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/openai/_utils/errors.py +2 -2
- mirascope/llm/providers/openai/completions/_utils/encode.py +20 -16
- mirascope/llm/providers/openai/completions/base_provider.py +40 -11
- mirascope/llm/providers/openai/provider.py +40 -10
- mirascope/llm/providers/openai/responses/_utils/__init__.py +2 -0
- mirascope/llm/providers/openai/responses/_utils/decode.py +19 -6
- mirascope/llm/providers/openai/responses/_utils/encode.py +22 -10
- mirascope/llm/providers/openai/responses/provider.py +56 -18
- mirascope/llm/providers/provider_registry.py +93 -19
- 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 +115 -25
- 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 +9 -4
- mirascope/llm/tools/tool_schema.py +17 -6
- mirascope/llm/tools/toolkit.py +35 -27
- mirascope/llm/tools/tools.py +45 -20
- mirascope/ops/__init__.py +4 -0
- mirascope/ops/_internal/closure.py +4 -1
- mirascope/ops/_internal/configuration.py +82 -31
- mirascope/ops/_internal/exporters/exporters.py +55 -35
- mirascope/ops/_internal/exporters/utils.py +37 -0
- 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 -1242
- 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 +18 -0
- mirascope/ops/_internal/traced_functions.py +125 -10
- mirascope/ops/_internal/tracing.py +78 -1
- mirascope/ops/_internal/utils.py +60 -4
- mirascope/ops/_internal/versioned_functions.py +1 -1
- {mirascope-2.0.0a6.dist-info → mirascope-2.0.2.dist-info}/METADATA +12 -11
- mirascope-2.0.2.dist-info/RECORD +424 -0
- {mirascope-2.0.0a6.dist-info → mirascope-2.0.2.dist-info}/licenses/LICENSE +1 -1
- mirascope-2.0.0a6.dist-info/RECORD +0 -316
- {mirascope-2.0.0a6.dist-info → mirascope-2.0.2.dist-info}/WHEEL +0 -0
|
@@ -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)
|
|
@@ -505,6 +514,26 @@ class BaseSyncStreamResponse(BaseStreamResponse[ChunkIterator, ToolkitT, Formatt
|
|
|
505
514
|
for _ in self.chunk_stream():
|
|
506
515
|
pass
|
|
507
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
|
+
|
|
508
537
|
def pretty_stream(self) -> Iterator[str]:
|
|
509
538
|
"""Stream a readable representation of the stream_response as text.
|
|
510
539
|
|
|
@@ -527,26 +556,46 @@ class BaseSyncStreamResponse(BaseStreamResponse[ChunkIterator, ToolkitT, Formatt
|
|
|
527
556
|
printed = True
|
|
528
557
|
yield pretty
|
|
529
558
|
|
|
530
|
-
if not printed:
|
|
531
|
-
yield "**[No Content]**"
|
|
532
|
-
|
|
533
559
|
def structured_stream(
|
|
534
560
|
self,
|
|
535
561
|
) -> Iterator[Partial[FormattableT]]:
|
|
536
|
-
"""
|
|
562
|
+
"""Drive the stream forward, yielding partial formatted outputs as they arrive.
|
|
537
563
|
|
|
538
|
-
|
|
539
|
-
|
|
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.
|
|
540
567
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
|
545
573
|
|
|
546
574
|
Fully iterating through this iterator will fully consume the underlying stream,
|
|
547
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).
|
|
548
584
|
"""
|
|
549
|
-
|
|
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
|
|
550
599
|
|
|
551
600
|
|
|
552
601
|
class BaseAsyncStreamResponse(
|
|
@@ -686,6 +735,27 @@ class BaseAsyncStreamResponse(
|
|
|
686
735
|
async for _ in self.chunk_stream():
|
|
687
736
|
pass
|
|
688
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
|
+
|
|
689
759
|
async def pretty_stream(self) -> AsyncIterator[str]:
|
|
690
760
|
"""Stream a readable representation of the stream_response as text.
|
|
691
761
|
|
|
@@ -708,23 +778,43 @@ class BaseAsyncStreamResponse(
|
|
|
708
778
|
printed = True
|
|
709
779
|
yield pretty
|
|
710
780
|
|
|
711
|
-
|
|
712
|
-
yield "**[No Content]**"
|
|
713
|
-
|
|
714
|
-
def structured_stream(
|
|
781
|
+
async def structured_stream(
|
|
715
782
|
self,
|
|
716
783
|
) -> AsyncIterator[Partial[FormattableT]]:
|
|
717
|
-
"""
|
|
784
|
+
"""Drive the stream forward, yielding partial formatted outputs as they arrive.
|
|
718
785
|
|
|
719
|
-
|
|
720
|
-
|
|
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.
|
|
721
789
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
|
726
795
|
|
|
727
796
|
Fully iterating through this iterator will fully consume the underlying stream,
|
|
728
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).
|
|
729
806
|
"""
|
|
730
|
-
|
|
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
|
|
mirascope/llm/tools/decorator.py
CHANGED
|
@@ -14,8 +14,12 @@ from .tools import AsyncContextTool, AsyncTool, ContextTool, Tool
|
|
|
14
14
|
class ToolDecorator:
|
|
15
15
|
"""Protocol for the tool decorator."""
|
|
16
16
|
|
|
17
|
-
strict: bool
|
|
18
|
-
"""Whether to use strict tool calling, if supported by the provider.
|
|
17
|
+
strict: bool | None = None
|
|
18
|
+
"""Whether to use strict tool calling, if supported by the provider.
|
|
19
|
+
|
|
20
|
+
If set to None, then it will use the provider's default setting (usually the
|
|
21
|
+
strictest possible).
|
|
22
|
+
"""
|
|
19
23
|
|
|
20
24
|
@overload
|
|
21
25
|
def __call__( # pyright:ignore[reportOverlappingOverload]
|
|
@@ -106,7 +110,7 @@ def tool(__fn: ToolFn[P, JsonableCovariantT]) -> Tool[P, JsonableCovariantT]:
|
|
|
106
110
|
|
|
107
111
|
|
|
108
112
|
@overload
|
|
109
|
-
def tool(*, strict: bool =
|
|
113
|
+
def tool(*, strict: bool | None = None) -> ToolDecorator:
|
|
110
114
|
"""Overload for setting non-default arguments."""
|
|
111
115
|
...
|
|
112
116
|
|
|
@@ -118,7 +122,7 @@ def tool(
|
|
|
118
122
|
| AsyncToolFn[P, JsonableCovariantT]
|
|
119
123
|
| None = None,
|
|
120
124
|
*,
|
|
121
|
-
strict: bool =
|
|
125
|
+
strict: bool | None = None,
|
|
122
126
|
) -> (
|
|
123
127
|
ContextTool[DepsT, JsonableCovariantT, P]
|
|
124
128
|
| AsyncContextTool[DepsT, JsonableCovariantT, P]
|
|
@@ -137,6 +141,7 @@ def tool(
|
|
|
137
141
|
|
|
138
142
|
Args:
|
|
139
143
|
strict: Whether the tool should use strict mode when supported by the model.
|
|
144
|
+
If None, uses provider's default (usually as strict as possible).
|
|
140
145
|
|
|
141
146
|
Returns:
|
|
142
147
|
A decorator function that converts the function into a Tool or ContextTool.
|
|
@@ -22,6 +22,7 @@ from docstring_parser import parse
|
|
|
22
22
|
from pydantic import BaseModel, Field, create_model
|
|
23
23
|
from pydantic.fields import FieldInfo
|
|
24
24
|
|
|
25
|
+
from ..._utils import copy_function_metadata
|
|
25
26
|
from ..content import ToolCall
|
|
26
27
|
from ..types import Jsonable
|
|
27
28
|
from .protocols import AsyncContextToolFn, AsyncToolFn, ContextToolFn, ToolFn
|
|
@@ -157,8 +158,15 @@ class ToolSchema(Generic[ToolFnT]):
|
|
|
157
158
|
it should **not be modified** after the ToolSchema is created.
|
|
158
159
|
"""
|
|
159
160
|
|
|
160
|
-
strict: bool
|
|
161
|
-
"""Whether the tool should use strict mode when supported by the model.
|
|
161
|
+
strict: bool | None
|
|
162
|
+
"""Whether the tool should use strict mode when supported by the model.
|
|
163
|
+
|
|
164
|
+
If set to None, will use the provider's default setting (usually as strict as
|
|
165
|
+
possible).
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
__name__: str
|
|
169
|
+
"""The name of the underlying function (preserved for decorator stacking)."""
|
|
162
170
|
|
|
163
171
|
def __hash__(self) -> int:
|
|
164
172
|
if not hasattr(self, "_hash"):
|
|
@@ -179,7 +187,7 @@ class ToolSchema(Generic[ToolFnT]):
|
|
|
179
187
|
description: str,
|
|
180
188
|
parameters: ToolParameterSchema,
|
|
181
189
|
*,
|
|
182
|
-
strict: bool =
|
|
190
|
+
strict: bool | None = None,
|
|
183
191
|
) -> None:
|
|
184
192
|
"""Create a `ToolSchema` with the provided values.
|
|
185
193
|
|
|
@@ -188,20 +196,22 @@ class ToolSchema(Generic[ToolFnT]):
|
|
|
188
196
|
name: The name of the tool
|
|
189
197
|
description: Description of what the tool does
|
|
190
198
|
parameters: JSON Schema describing the parameters accepted by the tool
|
|
191
|
-
strict: Whether the tool should use strict mode when supported
|
|
199
|
+
strict: Whether the tool should use strict mode when supported.
|
|
200
|
+
If None, uses provider's default (usually as strict as possible).
|
|
192
201
|
"""
|
|
193
202
|
self.fn = fn
|
|
194
203
|
self.name = name
|
|
195
204
|
self.description = description
|
|
196
205
|
self.parameters = parameters
|
|
197
206
|
self.strict = strict
|
|
207
|
+
copy_function_metadata(self, fn)
|
|
198
208
|
|
|
199
209
|
@classmethod
|
|
200
210
|
def from_function(
|
|
201
211
|
cls,
|
|
202
212
|
fn: AnyToolFn,
|
|
203
213
|
*,
|
|
204
|
-
strict: bool =
|
|
214
|
+
strict: bool | None = None,
|
|
205
215
|
is_context_tool: bool = False,
|
|
206
216
|
) -> ToolSchema[AnyToolFn]:
|
|
207
217
|
"""Create a `ToolSchema` by inspecting a function and its docstring.
|
|
@@ -212,7 +222,8 @@ class ToolSchema(Generic[ToolFnT]):
|
|
|
212
222
|
|
|
213
223
|
Args:
|
|
214
224
|
fn: The function to extract schema from
|
|
215
|
-
strict: Whether the tool should use strict mode when supported
|
|
225
|
+
strict: Whether the tool should use strict mode when supported.
|
|
226
|
+
If None, uses provider's default (usually as strict as possible).
|
|
216
227
|
is_context_tool: Whether this is a context tool (skips the context parameter)
|
|
217
228
|
|
|
218
229
|
Returns:
|
mirascope/llm/tools/toolkit.py
CHANGED
|
@@ -60,7 +60,7 @@ class BaseToolkit(Generic[ToolSchemaT]):
|
|
|
60
60
|
"""
|
|
61
61
|
tool = self.tools_dict.get(tool_call.name, None)
|
|
62
62
|
if not tool:
|
|
63
|
-
raise ToolNotFoundError(
|
|
63
|
+
raise ToolNotFoundError(tool_call.name)
|
|
64
64
|
return tool
|
|
65
65
|
|
|
66
66
|
|
|
@@ -75,12 +75,14 @@ class Toolkit(BaseToolkit[Tool]):
|
|
|
75
75
|
|
|
76
76
|
Returns:
|
|
77
77
|
The output from executing the `Tool`.
|
|
78
|
-
|
|
79
|
-
Raises:
|
|
80
|
-
ToolNotFoundError: If the requested tool is not found.
|
|
81
78
|
"""
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
try:
|
|
80
|
+
tool = self.get(tool_call)
|
|
81
|
+
return tool.execute(tool_call)
|
|
82
|
+
except ToolNotFoundError as e:
|
|
83
|
+
return ToolOutput(
|
|
84
|
+
id=tool_call.id, result=str(e), error=e, name=tool_call.name
|
|
85
|
+
)
|
|
84
86
|
|
|
85
87
|
|
|
86
88
|
class AsyncToolkit(BaseToolkit[AsyncTool]):
|
|
@@ -94,12 +96,14 @@ class AsyncToolkit(BaseToolkit[AsyncTool]):
|
|
|
94
96
|
|
|
95
97
|
Returns:
|
|
96
98
|
The output from executing the `AsyncTool`.
|
|
97
|
-
|
|
98
|
-
Raises:
|
|
99
|
-
ToolNotFoundError: If the requested tool is not found.
|
|
100
99
|
"""
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
try:
|
|
101
|
+
tool = self.get(tool_call)
|
|
102
|
+
return await tool.execute(tool_call)
|
|
103
|
+
except ToolNotFoundError as e:
|
|
104
|
+
return ToolOutput(
|
|
105
|
+
id=tool_call.id, result=str(e), error=e, name=tool_call.name
|
|
106
|
+
)
|
|
103
107
|
|
|
104
108
|
|
|
105
109
|
class ContextToolkit(BaseToolkit[Tool | ContextTool[DepsT]], Generic[DepsT]):
|
|
@@ -114,15 +118,17 @@ class ContextToolkit(BaseToolkit[Tool | ContextTool[DepsT]], Generic[DepsT]):
|
|
|
114
118
|
|
|
115
119
|
Returns:
|
|
116
120
|
The output from executing the `ContextTool`.
|
|
117
|
-
|
|
118
|
-
Raises:
|
|
119
|
-
ToolNotFoundError: If the requested tool is not found.
|
|
120
121
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
try:
|
|
123
|
+
tool = self.get(tool_call)
|
|
124
|
+
if isinstance(tool, ContextTool):
|
|
125
|
+
return tool.execute(ctx, tool_call)
|
|
126
|
+
else:
|
|
127
|
+
return tool.execute(tool_call)
|
|
128
|
+
except ToolNotFoundError as e:
|
|
129
|
+
return ToolOutput(
|
|
130
|
+
id=tool_call.id, result=str(e), error=e, name=tool_call.name
|
|
131
|
+
)
|
|
126
132
|
|
|
127
133
|
|
|
128
134
|
class AsyncContextToolkit(
|
|
@@ -141,12 +147,14 @@ class AsyncContextToolkit(
|
|
|
141
147
|
|
|
142
148
|
Returns:
|
|
143
149
|
The output from executing the `AsyncContextTool`.
|
|
144
|
-
|
|
145
|
-
Raises:
|
|
146
|
-
ToolNotFoundError: If the requested tool is not found.
|
|
147
150
|
"""
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
try:
|
|
152
|
+
tool = self.get(tool_call)
|
|
153
|
+
if isinstance(tool, AsyncContextTool):
|
|
154
|
+
return await tool.execute(ctx, tool_call)
|
|
155
|
+
else:
|
|
156
|
+
return await tool.execute(tool_call)
|
|
157
|
+
except ToolNotFoundError as e:
|
|
158
|
+
return ToolOutput(
|
|
159
|
+
id=tool_call.id, result=str(e), error=e, name=tool_call.name
|
|
160
|
+
)
|