pydantic-ai-slim 0.4.9__tar.gz → 0.4.11__tar.gz
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.
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/PKG-INFO +3 -3
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_agent_graph.py +21 -19
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_parts_manager.py +8 -9
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_thinking_part.py +7 -12
- pydantic_ai_slim-0.4.11/pydantic_ai/ag_ui.py +688 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/agent.py +88 -85
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/messages.py +2 -2
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/__init__.py +2 -2
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/cohere.py +1 -1
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/gemini.py +1 -1
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/google.py +1 -1
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/groq.py +7 -3
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/huggingface.py +7 -2
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/mistral.py +1 -1
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/openai.py +10 -5
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/test.py +3 -1
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/__init__.py +3 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/anthropic.py +1 -1
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/vercel.py +8 -2
- pydantic_ai_slim-0.4.9/pydantic_ai/ag_ui.py +0 -658
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/.gitignore +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/LICENSE +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/README.md +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/__init__.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/__main__.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_a2a.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_cli.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_function_schema.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_griffe.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_mcp.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_output.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_run_context.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_system_prompt.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_tool_manager.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/_utils.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/common_tools/__init__.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/common_tools/duckduckgo.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/common_tools/tavily.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/direct.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/exceptions.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/ext/__init__.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/ext/aci.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/ext/langchain.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/format_as_xml.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/format_prompt.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/mcp.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/anthropic.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/bedrock.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/fallback.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/function.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/instrumented.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/mcp_sampling.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/models/wrapper.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/output.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/_json_schema.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/amazon.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/cohere.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/deepseek.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/google.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/grok.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/meta.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/mistral.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/moonshotai.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/openai.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/profiles/qwen.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/__init__.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/anthropic.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/azure.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/bedrock.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/cohere.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/deepseek.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/fireworks.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/github.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/google.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/google_gla.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/google_vertex.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/grok.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/groq.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/heroku.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/huggingface.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/mistral.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/moonshotai.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/openai.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/openrouter.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/providers/together.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/py.typed +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/result.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/retries.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/settings.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/tools.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/toolsets/__init__.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/toolsets/abstract.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/toolsets/combined.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/toolsets/deferred.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/toolsets/filtered.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/toolsets/function.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/toolsets/prefixed.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/toolsets/prepared.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/toolsets/renamed.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/toolsets/wrapper.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pydantic_ai/usage.py +0 -0
- {pydantic_ai_slim-0.4.9 → pydantic_ai_slim-0.4.11}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydantic-ai-slim
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.11
|
|
4
4
|
Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
|
|
5
5
|
Author-email: Samuel Colvin <samuel@pydantic.dev>, Marcelo Trylesinski <marcelotryle@gmail.com>, David Montague <david@pydantic.dev>, Alex Hall <alex@pydantic.dev>, Douwe Maan <douwe@pydantic.dev>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -30,7 +30,7 @@ Requires-Dist: exceptiongroup; python_version < '3.11'
|
|
|
30
30
|
Requires-Dist: griffe>=1.3.2
|
|
31
31
|
Requires-Dist: httpx>=0.27
|
|
32
32
|
Requires-Dist: opentelemetry-api>=1.28.0
|
|
33
|
-
Requires-Dist: pydantic-graph==0.4.
|
|
33
|
+
Requires-Dist: pydantic-graph==0.4.11
|
|
34
34
|
Requires-Dist: pydantic>=2.10
|
|
35
35
|
Requires-Dist: typing-inspection>=0.4.0
|
|
36
36
|
Provides-Extra: a2a
|
|
@@ -51,7 +51,7 @@ Requires-Dist: cohere>=5.16.0; (platform_system != 'Emscripten') and extra == 'c
|
|
|
51
51
|
Provides-Extra: duckduckgo
|
|
52
52
|
Requires-Dist: ddgs>=9.0.0; extra == 'duckduckgo'
|
|
53
53
|
Provides-Extra: evals
|
|
54
|
-
Requires-Dist: pydantic-evals==0.4.
|
|
54
|
+
Requires-Dist: pydantic-evals==0.4.11; extra == 'evals'
|
|
55
55
|
Provides-Extra: google
|
|
56
56
|
Requires-Dist: google-genai>=1.24.0; extra == 'google'
|
|
57
57
|
Provides-Extra: groq
|
|
@@ -659,11 +659,11 @@ async def process_function_tools( # noqa: C901
|
|
|
659
659
|
for call in calls_to_run:
|
|
660
660
|
yield _messages.FunctionToolCallEvent(call)
|
|
661
661
|
|
|
662
|
-
|
|
662
|
+
user_parts_by_index: dict[int, list[_messages.UserPromptPart]] = defaultdict(list)
|
|
663
663
|
|
|
664
664
|
if calls_to_run:
|
|
665
665
|
# Run all tool tasks in parallel
|
|
666
|
-
|
|
666
|
+
tool_parts_by_index: dict[int, _messages.ModelRequestPart] = {}
|
|
667
667
|
with ctx.deps.tracer.start_as_current_span(
|
|
668
668
|
'running tools',
|
|
669
669
|
attributes={
|
|
@@ -681,15 +681,16 @@ async def process_function_tools( # noqa: C901
|
|
|
681
681
|
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
|
|
682
682
|
for task in done:
|
|
683
683
|
index = tasks.index(task)
|
|
684
|
-
|
|
685
|
-
yield _messages.FunctionToolResultEvent(
|
|
684
|
+
tool_part, tool_user_parts = task.result()
|
|
685
|
+
yield _messages.FunctionToolResultEvent(tool_part)
|
|
686
686
|
|
|
687
|
-
|
|
687
|
+
tool_parts_by_index[index] = tool_part
|
|
688
|
+
user_parts_by_index[index] = tool_user_parts
|
|
688
689
|
|
|
689
690
|
# We append the results at the end, rather than as they are received, to retain a consistent ordering
|
|
690
691
|
# This is mostly just to simplify testing
|
|
691
|
-
for k in sorted(
|
|
692
|
-
output_parts.
|
|
692
|
+
for k in sorted(tool_parts_by_index):
|
|
693
|
+
output_parts.append(tool_parts_by_index[k])
|
|
693
694
|
|
|
694
695
|
# Finally, we handle deferred tool calls
|
|
695
696
|
for call in tool_calls_by_kind['deferred']:
|
|
@@ -704,7 +705,8 @@ async def process_function_tools( # noqa: C901
|
|
|
704
705
|
else:
|
|
705
706
|
yield _messages.FunctionToolCallEvent(call)
|
|
706
707
|
|
|
707
|
-
|
|
708
|
+
for k in sorted(user_parts_by_index):
|
|
709
|
+
output_parts.extend(user_parts_by_index[k])
|
|
708
710
|
|
|
709
711
|
if final_result:
|
|
710
712
|
output_final_result.append(final_result)
|
|
@@ -713,18 +715,18 @@ async def process_function_tools( # noqa: C901
|
|
|
713
715
|
async def _call_function_tool(
|
|
714
716
|
tool_manager: ToolManager[DepsT],
|
|
715
717
|
tool_call: _messages.ToolCallPart,
|
|
716
|
-
) -> tuple[_messages.ToolReturnPart | _messages.RetryPromptPart, list[_messages.
|
|
718
|
+
) -> tuple[_messages.ToolReturnPart | _messages.RetryPromptPart, list[_messages.UserPromptPart]]:
|
|
717
719
|
try:
|
|
718
720
|
tool_result = await tool_manager.handle_call(tool_call)
|
|
719
721
|
except ToolRetryError as e:
|
|
720
722
|
return (e.tool_retry, [])
|
|
721
723
|
|
|
722
|
-
|
|
724
|
+
tool_part = _messages.ToolReturnPart(
|
|
723
725
|
tool_name=tool_call.tool_name,
|
|
724
726
|
content=tool_result,
|
|
725
727
|
tool_call_id=tool_call.tool_call_id,
|
|
726
728
|
)
|
|
727
|
-
|
|
729
|
+
user_parts: list[_messages.UserPromptPart] = []
|
|
728
730
|
|
|
729
731
|
if isinstance(tool_result, _messages.ToolReturn):
|
|
730
732
|
if (
|
|
@@ -740,12 +742,12 @@ async def _call_function_tool(
|
|
|
740
742
|
f'Please use `content` instead.'
|
|
741
743
|
)
|
|
742
744
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
+
tool_part.content = tool_result.return_value # type: ignore
|
|
746
|
+
tool_part.metadata = tool_result.metadata
|
|
745
747
|
if tool_result.content:
|
|
746
|
-
|
|
748
|
+
user_parts.append(
|
|
747
749
|
_messages.UserPromptPart(
|
|
748
|
-
content=
|
|
750
|
+
content=tool_result.content,
|
|
749
751
|
part_kind='user-prompt',
|
|
750
752
|
)
|
|
751
753
|
)
|
|
@@ -763,7 +765,7 @@ async def _call_function_tool(
|
|
|
763
765
|
else:
|
|
764
766
|
identifier = multi_modal_content_identifier(content.url)
|
|
765
767
|
|
|
766
|
-
|
|
768
|
+
user_parts.append(
|
|
767
769
|
_messages.UserPromptPart(
|
|
768
770
|
content=[f'This is file {identifier}:', content],
|
|
769
771
|
part_kind='user-prompt',
|
|
@@ -775,11 +777,11 @@ async def _call_function_tool(
|
|
|
775
777
|
|
|
776
778
|
if isinstance(tool_result, list):
|
|
777
779
|
contents = cast(list[Any], tool_result)
|
|
778
|
-
|
|
780
|
+
tool_part.content = [process_content(content) for content in contents]
|
|
779
781
|
else:
|
|
780
|
-
|
|
782
|
+
tool_part.content = process_content(tool_result)
|
|
781
783
|
|
|
782
|
-
return (
|
|
784
|
+
return (tool_part, user_parts)
|
|
783
785
|
|
|
784
786
|
|
|
785
787
|
@dataclasses.dataclass
|
|
@@ -17,7 +17,6 @@ from collections.abc import Hashable
|
|
|
17
17
|
from dataclasses import dataclass, field, replace
|
|
18
18
|
from typing import Any, Union
|
|
19
19
|
|
|
20
|
-
from pydantic_ai._thinking_part import END_THINK_TAG, START_THINK_TAG
|
|
21
20
|
from pydantic_ai.exceptions import UnexpectedModelBehavior
|
|
22
21
|
from pydantic_ai.messages import (
|
|
23
22
|
ModelResponsePart,
|
|
@@ -72,7 +71,7 @@ class ModelResponsePartsManager:
|
|
|
72
71
|
*,
|
|
73
72
|
vendor_part_id: VendorId | None,
|
|
74
73
|
content: str,
|
|
75
|
-
|
|
74
|
+
thinking_tags: tuple[str, str] | None = None,
|
|
76
75
|
) -> ModelResponseStreamEvent | None:
|
|
77
76
|
"""Handle incoming text content, creating or updating a TextPart in the manager as appropriate.
|
|
78
77
|
|
|
@@ -85,7 +84,7 @@ class ModelResponsePartsManager:
|
|
|
85
84
|
of text. If None, a new part will be created unless the latest part is already
|
|
86
85
|
a TextPart.
|
|
87
86
|
content: The text content to append to the appropriate TextPart.
|
|
88
|
-
|
|
87
|
+
thinking_tags: If provided, will handle content between the thinking tags as thinking parts.
|
|
89
88
|
|
|
90
89
|
Returns:
|
|
91
90
|
- A `PartStartEvent` if a new part was created.
|
|
@@ -110,10 +109,10 @@ class ModelResponsePartsManager:
|
|
|
110
109
|
if part_index is not None:
|
|
111
110
|
existing_part = self._parts[part_index]
|
|
112
111
|
|
|
113
|
-
if
|
|
114
|
-
# We may be building a thinking part instead of a text part if we had previously seen a
|
|
115
|
-
if content ==
|
|
116
|
-
# When we see
|
|
112
|
+
if thinking_tags and isinstance(existing_part, ThinkingPart):
|
|
113
|
+
# We may be building a thinking part instead of a text part if we had previously seen a thinking tag
|
|
114
|
+
if content == thinking_tags[1]:
|
|
115
|
+
# When we see the thinking end tag, we're done with the thinking part and the next text delta will need a new part
|
|
117
116
|
self._vendor_id_to_part_index.pop(vendor_part_id)
|
|
118
117
|
return None
|
|
119
118
|
else:
|
|
@@ -123,8 +122,8 @@ class ModelResponsePartsManager:
|
|
|
123
122
|
else:
|
|
124
123
|
raise UnexpectedModelBehavior(f'Cannot apply a text delta to {existing_part=}')
|
|
125
124
|
|
|
126
|
-
if
|
|
127
|
-
# When we see a
|
|
125
|
+
if thinking_tags and content == thinking_tags[0]:
|
|
126
|
+
# When we see a thinking start tag (which is a single token), we'll build a new thinking part instead
|
|
128
127
|
self._vendor_id_to_part_index.pop(vendor_part_id, None)
|
|
129
128
|
return self.handle_thinking_delta(vendor_part_id=vendor_part_id, content='')
|
|
130
129
|
|
|
@@ -2,35 +2,30 @@ from __future__ import annotations as _annotations
|
|
|
2
2
|
|
|
3
3
|
from pydantic_ai.messages import TextPart, ThinkingPart
|
|
4
4
|
|
|
5
|
-
START_THINK_TAG = '<think>'
|
|
6
|
-
END_THINK_TAG = '</think>'
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
def split_content_into_text_and_thinking(content: str) -> list[ThinkingPart | TextPart]:
|
|
6
|
+
def split_content_into_text_and_thinking(content: str, thinking_tags: tuple[str, str]) -> list[ThinkingPart | TextPart]:
|
|
10
7
|
"""Split a string into text and thinking parts.
|
|
11
8
|
|
|
12
9
|
Some models don't return the thinking part as a separate part, but rather as a tag in the content.
|
|
13
10
|
This function splits the content into text and thinking parts.
|
|
14
|
-
|
|
15
|
-
We use the `<think>` tag because that's how Groq uses it in the `raw` format, so instead of using `<Thinking>` or
|
|
16
|
-
something else, we just match the tag to make it easier for other models that don't support the `ThinkingPart`.
|
|
17
11
|
"""
|
|
12
|
+
start_tag, end_tag = thinking_tags
|
|
18
13
|
parts: list[ThinkingPart | TextPart] = []
|
|
19
14
|
|
|
20
|
-
start_index = content.find(
|
|
15
|
+
start_index = content.find(start_tag)
|
|
21
16
|
while start_index >= 0:
|
|
22
|
-
before_think, content = content[:start_index], content[start_index + len(
|
|
17
|
+
before_think, content = content[:start_index], content[start_index + len(start_tag) :]
|
|
23
18
|
if before_think:
|
|
24
19
|
parts.append(TextPart(content=before_think))
|
|
25
|
-
end_index = content.find(
|
|
20
|
+
end_index = content.find(end_tag)
|
|
26
21
|
if end_index >= 0:
|
|
27
|
-
think_content, content = content[:end_index], content[end_index + len(
|
|
22
|
+
think_content, content = content[:end_index], content[end_index + len(end_tag) :]
|
|
28
23
|
parts.append(ThinkingPart(content=think_content))
|
|
29
24
|
else:
|
|
30
25
|
# We lose the `<think>` tag, but it shouldn't matter.
|
|
31
26
|
parts.append(TextPart(content=content))
|
|
32
27
|
content = ''
|
|
33
|
-
start_index = content.find(
|
|
28
|
+
start_index = content.find(start_tag)
|
|
34
29
|
if content:
|
|
35
30
|
parts.append(TextPart(content=content))
|
|
36
31
|
return parts
|