pyagentic-core 2.3.0a1__tar.gz → 2.3.0a3__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.
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/PKG-INFO +1 -1
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/_agent/_agent.py +26 -9
- pyagentic_core-2.3.0a3/pyagentic/_utils/_image.py +20 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/llm/_openai.py +31 -25
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/llm/_provider.py +3 -1
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/models/llm.py +6 -1
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/models/response.py +17 -1
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic_core.egg-info/PKG-INFO +1 -1
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic_core.egg-info/SOURCES.txt +1 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyproject.toml +1 -1
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/LICENSE +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/README.md +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/__init__.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/__init__.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/_agent/__init__.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/_agent/_agent_linking.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/_agent/_agent_state.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/_exceptions.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/_info.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/_metaclasses.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/_ref.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/_spec.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/_state.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/_tool.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_base/_validation.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_utils/_typing.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/_utils/_warnings.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/llm/__init__.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/llm/_anthropic.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/llm/_gemini.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/llm/_mock.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/llm/_openaiv1.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/logging.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/models/tracing.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/policies/__init__.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/policies/_events.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/policies/_policy.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/tracing/__init__.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/tracing/_basic.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/tracing/_langfuse.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/tracing/_tracer.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic/updates.py +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic_core.egg-info/dependency_links.txt +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic_core.egg-info/requires.txt +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic_core.egg-info/top_level.txt +0 -0
- {pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/setup.cfg +0 -0
|
@@ -16,6 +16,7 @@ from typing import (
|
|
|
16
16
|
|
|
17
17
|
from transitions import Machine
|
|
18
18
|
from pydantic import BaseModel, ValidationError
|
|
19
|
+
from PIL.Image import Image
|
|
19
20
|
|
|
20
21
|
from pyagentic.logging import get_logger
|
|
21
22
|
from pyagentic._base._tool import _ToolDefinition, tool
|
|
@@ -431,15 +432,31 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
431
432
|
logger.exception(e)
|
|
432
433
|
result = f"Tool `{tool_call.name}` failed: {e}. Please kindly state to the user that is failed, provide state, and ask if they want to try again." # noqa E501
|
|
433
434
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
435
|
+
# Handle image results - create a message with the image attached
|
|
436
|
+
if isinstance(result, Image):
|
|
437
|
+
stringified_result = f"[Image returned by {tool_call.name}]"
|
|
438
|
+
# Add tool result message
|
|
439
|
+
self.state._messages.append(
|
|
440
|
+
self.provider.to_tool_call_result_message(
|
|
441
|
+
result=stringified_result, id_=tool_call.id
|
|
442
|
+
)
|
|
443
|
+
)
|
|
444
|
+
# Add the image as a user message so the LLM can see it
|
|
445
|
+
self.state._messages.append(
|
|
446
|
+
Message(role="user", content=f"Image from {tool_call.name}:", image=result)
|
|
447
|
+
)
|
|
448
|
+
else:
|
|
449
|
+
stringified_result = (
|
|
450
|
+
result.model_dump_json(indent=2)
|
|
451
|
+
if issubclass(result.__class__, BaseModel)
|
|
452
|
+
else str(result)
|
|
453
|
+
)
|
|
454
|
+
# Add tool result to conversation history for LLM
|
|
455
|
+
self.state._messages.append(
|
|
456
|
+
self.provider.to_tool_call_result_message(
|
|
457
|
+
result=stringified_result, id_=tool_call.id
|
|
458
|
+
)
|
|
459
|
+
)
|
|
443
460
|
|
|
444
461
|
if self.phases:
|
|
445
462
|
self.state._update_state_machine(phases=self.phases)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from PIL.Image import Image
|
|
2
|
+
import base64
|
|
3
|
+
import io
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _encode_image(image: Image) -> str:
|
|
7
|
+
"""
|
|
8
|
+
Convert a PIL Image to a base64-encoded data URL.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
image: PIL Image object to encode
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
Base64-encoded data URL string
|
|
15
|
+
"""
|
|
16
|
+
buffer = io.BytesIO()
|
|
17
|
+
image.save(buffer, format="PNG")
|
|
18
|
+
image_bytes = buffer.getvalue()
|
|
19
|
+
base64_image = base64.b64encode(image_bytes).decode("utf-8")
|
|
20
|
+
return f"data:image/png;base64,{base64_image}"
|
|
@@ -9,8 +9,6 @@ import openai
|
|
|
9
9
|
from openai.types.responses import Response as OpenAIResponse
|
|
10
10
|
from openai.types.responses import ParsedResponse as OpenAIParsedResponse
|
|
11
11
|
|
|
12
|
-
import base64
|
|
13
|
-
import io
|
|
14
12
|
from typing import List, Optional, Type
|
|
15
13
|
from pydantic import BaseModel
|
|
16
14
|
from PIL.Image import Image
|
|
@@ -19,6 +17,7 @@ from pyagentic._base._agent._agent_state import _AgentState
|
|
|
19
17
|
from pyagentic._base._tool import _ToolDefinition
|
|
20
18
|
from pyagentic.llm._provider import LLMProvider
|
|
21
19
|
from pyagentic.models.llm import ProviderInfo, LLMResponse, ToolCall, Message, UsageInfo
|
|
20
|
+
from pyagentic._utils._image import _encode_image
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
class OpenAIMessage(Message):
|
|
@@ -88,27 +87,45 @@ class OpenAIProvider(LLMProvider):
|
|
|
88
87
|
"""
|
|
89
88
|
return OpenAIMessage(type="function_call_output", call_id=id_, output=result)
|
|
90
89
|
|
|
91
|
-
def
|
|
90
|
+
def _convert_messages_to_openai_format(self, messages: List[Message]) -> List[dict]:
|
|
92
91
|
"""
|
|
93
|
-
Convert
|
|
92
|
+
Convert pyagentic messages to OpenAI's API format.
|
|
93
|
+
|
|
94
|
+
Handles messages with images by converting them to OpenAI's content array format.
|
|
94
95
|
|
|
95
96
|
Args:
|
|
96
|
-
|
|
97
|
+
messages: List of Message objects from the agent state
|
|
97
98
|
|
|
98
99
|
Returns:
|
|
99
|
-
|
|
100
|
+
List of message dictionaries in OpenAI API format
|
|
100
101
|
"""
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
openai_messages = []
|
|
103
|
+
|
|
104
|
+
for message in messages:
|
|
105
|
+
msg_dict = message.to_dict()
|
|
106
|
+
|
|
107
|
+
# If message has an image, convert to OpenAI's content array format
|
|
108
|
+
if message.image is not None:
|
|
109
|
+
image_url = _encode_image(message.image)
|
|
110
|
+
|
|
111
|
+
# Build content array with text and image
|
|
112
|
+
content = []
|
|
113
|
+
if message.content:
|
|
114
|
+
content.append({"type": "text", "text": message.content})
|
|
115
|
+
content.append({"type": "image_url", "image_url": {"url": image_url}})
|
|
116
|
+
|
|
117
|
+
msg_dict["content"] = content
|
|
118
|
+
# Remove the image field as it's now in content
|
|
119
|
+
msg_dict.pop("image", None)
|
|
120
|
+
|
|
121
|
+
openai_messages.append(msg_dict)
|
|
122
|
+
|
|
123
|
+
return openai_messages
|
|
106
124
|
|
|
107
125
|
async def generate(
|
|
108
126
|
self,
|
|
109
127
|
state: _AgentState,
|
|
110
128
|
*,
|
|
111
|
-
images: Optional[Image] = None,
|
|
112
129
|
tool_defs: Optional[List[_ToolDefinition]] = None,
|
|
113
130
|
response_format: Optional[Type[BaseModel]] = None,
|
|
114
131
|
**kwargs,
|
|
@@ -122,7 +139,6 @@ class OpenAIProvider(LLMProvider):
|
|
|
122
139
|
|
|
123
140
|
Args:
|
|
124
141
|
state: Agent state containing conversation history and system messages
|
|
125
|
-
images: Optional PIL Image to include in the request
|
|
126
142
|
tool_defs: List of available tools the model can call
|
|
127
143
|
response_format: Optional Pydantic model for structured output
|
|
128
144
|
**kwargs: Additional parameters for the OpenAI API call
|
|
@@ -134,18 +150,8 @@ class OpenAIProvider(LLMProvider):
|
|
|
134
150
|
if tool_defs is None:
|
|
135
151
|
tool_defs = []
|
|
136
152
|
|
|
137
|
-
#
|
|
138
|
-
input_messages =
|
|
139
|
-
|
|
140
|
-
# Add image if provided
|
|
141
|
-
if images:
|
|
142
|
-
image_url = self._encode_image(images)
|
|
143
|
-
# Add image as a user message with image content
|
|
144
|
-
image_message = {
|
|
145
|
-
"role": "user",
|
|
146
|
-
"content": [{"type": "image_url", "image_url": {"url": image_url}}],
|
|
147
|
-
}
|
|
148
|
-
input_messages.append(image_message)
|
|
153
|
+
# Convert messages to OpenAI format (handles images in messages)
|
|
154
|
+
input_messages = self._convert_messages_to_openai_format(state._messages)
|
|
149
155
|
|
|
150
156
|
if response_format:
|
|
151
157
|
response: OpenAIParsedResponse[Type[BaseModel]] = await self.client.responses.parse(
|
|
@@ -81,7 +81,6 @@ class LLMProvider(ABC):
|
|
|
81
81
|
self,
|
|
82
82
|
state: _AgentState,
|
|
83
83
|
*,
|
|
84
|
-
images: Optional[Image] = None,
|
|
85
84
|
tool_defs: Optional[list[_ToolDefinition]] = None,
|
|
86
85
|
response_format: Optional[Type[BaseModel]] = None,
|
|
87
86
|
**kwargs,
|
|
@@ -89,6 +88,9 @@ class LLMProvider(ABC):
|
|
|
89
88
|
"""
|
|
90
89
|
Generate a response from the language model.
|
|
91
90
|
|
|
91
|
+
Images can be included by adding them to messages in the agent state.
|
|
92
|
+
Each provider handles image conversion to their API format.
|
|
93
|
+
|
|
92
94
|
Args:
|
|
93
95
|
state: The agent state containing conversation history and system messages
|
|
94
96
|
tool_defs: Optional list of tool definitions the model can use
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from typing import Any, List, Optional
|
|
2
|
-
from pydantic import BaseModel
|
|
2
|
+
from pydantic import BaseModel, ConfigDict
|
|
3
|
+
|
|
4
|
+
from PIL.Image import Image
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
class Message(BaseModel):
|
|
@@ -10,9 +12,12 @@ class Message(BaseModel):
|
|
|
10
12
|
to a dictionary format for API communication.
|
|
11
13
|
"""
|
|
12
14
|
|
|
15
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
16
|
+
|
|
13
17
|
type: Optional[str] = None
|
|
14
18
|
role: Optional[str] = None
|
|
15
19
|
content: Optional[str] = None
|
|
20
|
+
image: Optional[Image] = None
|
|
16
21
|
|
|
17
22
|
def to_dict(self, exclude_none: bool = True):
|
|
18
23
|
"""
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
from pydantic import BaseModel, Field, create_model
|
|
1
|
+
from pydantic import BaseModel, Field, create_model, field_serializer
|
|
2
2
|
from typing import Type, Self, Union, Any
|
|
3
|
+
from PIL.Image import Image
|
|
3
4
|
|
|
4
5
|
from pyagentic._base._tool import _ToolDefinition
|
|
5
6
|
from pyagentic._base._agent._agent_state import _AgentState
|
|
6
7
|
|
|
7
8
|
from pyagentic._utils._typing import TypeCategory, analyze_type
|
|
9
|
+
from pyagentic._utils._image import _encode_image
|
|
8
10
|
from pyagentic.models.llm import ProviderInfo
|
|
9
11
|
|
|
10
12
|
|
|
@@ -20,6 +22,13 @@ class ToolResponse(BaseModel):
|
|
|
20
22
|
call_depth: int
|
|
21
23
|
output: Any
|
|
22
24
|
|
|
25
|
+
@field_serializer("output")
|
|
26
|
+
def serialize_output(self, value: Any, _info) -> Any:
|
|
27
|
+
"""Serialize Pillow images to base64-encoded data URLs."""
|
|
28
|
+
if isinstance(value, Image):
|
|
29
|
+
return _encode_image(value)
|
|
30
|
+
return value
|
|
31
|
+
|
|
23
32
|
@classmethod
|
|
24
33
|
def from_tool_def(cls, tool_def: _ToolDefinition) -> Type[Self]:
|
|
25
34
|
"""
|
|
@@ -77,6 +86,13 @@ class AgentResponse(BaseModel):
|
|
|
77
86
|
final_output: Union[str, Type[BaseModel]]
|
|
78
87
|
provider_info: ProviderInfo
|
|
79
88
|
|
|
89
|
+
@field_serializer("final_output")
|
|
90
|
+
def serialize_final_output(self, value: Any, _info) -> Any:
|
|
91
|
+
"""Serialize Pillow images to base64-encoded data URLs."""
|
|
92
|
+
if isinstance(value, Image):
|
|
93
|
+
return _encode_image(value)
|
|
94
|
+
return value
|
|
95
|
+
|
|
80
96
|
@classmethod
|
|
81
97
|
def from_agent_class(
|
|
82
98
|
cls,
|
|
@@ -17,6 +17,7 @@ pyagentic/_base/_agent/__init__.py
|
|
|
17
17
|
pyagentic/_base/_agent/_agent.py
|
|
18
18
|
pyagentic/_base/_agent/_agent_linking.py
|
|
19
19
|
pyagentic/_base/_agent/_agent_state.py
|
|
20
|
+
pyagentic/_utils/_image.py
|
|
20
21
|
pyagentic/_utils/_typing.py
|
|
21
22
|
pyagentic/_utils/_warnings.py
|
|
22
23
|
pyagentic/llm/__init__.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyagentic_core-2.3.0a1 → pyagentic_core-2.3.0a3}/pyagentic_core.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|