pyagentic-core 2.2.2__tar.gz → 2.3.0a2__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.2.2 → pyagentic_core-2.3.0a2}/PKG-INFO +2 -1
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_agent/_agent.py +22 -9
- pyagentic_core-2.3.0a2/pyagentic/_utils/_image.py +20 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/_openai.py +42 -2
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/_provider.py +4 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/models/llm.py +8 -2
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/models/response.py +17 -1
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic_core.egg-info/PKG-INFO +2 -1
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic_core.egg-info/SOURCES.txt +1 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic_core.egg-info/requires.txt +1 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyproject.toml +2 -1
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/LICENSE +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/README.md +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/__init__.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/__init__.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_agent/__init__.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_agent/_agent_linking.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_agent/_agent_state.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_exceptions.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_info.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_metaclasses.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_ref.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_spec.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_state.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_tool.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_validation.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_utils/_typing.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_utils/_warnings.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/__init__.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/_anthropic.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/_gemini.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/_mock.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/_openaiv1.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/logging.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/models/tracing.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/policies/__init__.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/policies/_events.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/policies/_policy.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/tracing/__init__.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/tracing/_basic.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/tracing/_langfuse.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/tracing/_tracer.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/updates.py +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic_core.egg-info/dependency_links.txt +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic_core.egg-info/top_level.txt +0 -0
- {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyagentic-core
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0a2
|
|
4
4
|
Summary: Build LLM Agents in a Pythonic way
|
|
5
5
|
Author-email: Ryan Mikulec <rmikulec.dev@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -21,6 +21,7 @@ Requires-Dist: c3linearize>=0.1.0
|
|
|
21
21
|
Requires-Dist: anthropic>=0.62.0
|
|
22
22
|
Requires-Dist: google-generativeai>=0.8.0
|
|
23
23
|
Requires-Dist: transitions>=0.9.3
|
|
24
|
+
Requires-Dist: pillow>=12.1.0
|
|
24
25
|
Dynamic: license-file
|
|
25
26
|
|
|
26
27
|
# PyAgentic
|
|
@@ -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,27 @@ 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(result=stringified_result, id_=tool_call.id)
|
|
441
|
+
)
|
|
442
|
+
# Add the image as a user message so the LLM can see it
|
|
443
|
+
self.state._messages.append(
|
|
444
|
+
Message(role="user", content=f"Image from {tool_call.name}:", image=result)
|
|
445
|
+
)
|
|
446
|
+
else:
|
|
447
|
+
stringified_result = (
|
|
448
|
+
result.model_dump_json(indent=2)
|
|
449
|
+
if issubclass(result.__class__, BaseModel)
|
|
450
|
+
else str(result)
|
|
451
|
+
)
|
|
452
|
+
# Add tool result to conversation history for LLM
|
|
453
|
+
self.state._messages.append(
|
|
454
|
+
self.provider.to_tool_call_result_message(result=stringified_result, id_=tool_call.id)
|
|
455
|
+
)
|
|
443
456
|
|
|
444
457
|
if self.phases:
|
|
445
458
|
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}"
|
|
@@ -11,11 +11,13 @@ from openai.types.responses import ParsedResponse as OpenAIParsedResponse
|
|
|
11
11
|
|
|
12
12
|
from typing import List, Optional, Type
|
|
13
13
|
from pydantic import BaseModel
|
|
14
|
+
from PIL.Image import Image
|
|
14
15
|
|
|
15
16
|
from pyagentic._base._agent._agent_state import _AgentState
|
|
16
17
|
from pyagentic._base._tool import _ToolDefinition
|
|
17
18
|
from pyagentic.llm._provider import LLMProvider
|
|
18
19
|
from pyagentic.models.llm import ProviderInfo, LLMResponse, ToolCall, Message, UsageInfo
|
|
20
|
+
from pyagentic._utils._image import _encode_image
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
class OpenAIMessage(Message):
|
|
@@ -85,6 +87,41 @@ class OpenAIProvider(LLMProvider):
|
|
|
85
87
|
"""
|
|
86
88
|
return OpenAIMessage(type="function_call_output", call_id=id_, output=result)
|
|
87
89
|
|
|
90
|
+
def _convert_messages_to_openai_format(self, messages: List[Message]) -> List[dict]:
|
|
91
|
+
"""
|
|
92
|
+
Convert pyagentic messages to OpenAI's API format.
|
|
93
|
+
|
|
94
|
+
Handles messages with images by converting them to OpenAI's content array format.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
messages: List of Message objects from the agent state
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
List of message dictionaries in OpenAI API format
|
|
101
|
+
"""
|
|
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
|
|
124
|
+
|
|
88
125
|
async def generate(
|
|
89
126
|
self,
|
|
90
127
|
state: _AgentState,
|
|
@@ -113,11 +150,14 @@ class OpenAIProvider(LLMProvider):
|
|
|
113
150
|
if tool_defs is None:
|
|
114
151
|
tool_defs = []
|
|
115
152
|
|
|
153
|
+
# Convert messages to OpenAI format (handles images in messages)
|
|
154
|
+
input_messages = self._convert_messages_to_openai_format(state._messages)
|
|
155
|
+
|
|
116
156
|
if response_format:
|
|
117
157
|
response: OpenAIParsedResponse[Type[BaseModel]] = await self.client.responses.parse(
|
|
118
158
|
model=self._model,
|
|
119
159
|
instructions=state.system_message,
|
|
120
|
-
input=
|
|
160
|
+
input=input_messages,
|
|
121
161
|
tools=[tool.to_openai_spec() for tool in tool_defs],
|
|
122
162
|
text_format=response_format,
|
|
123
163
|
**kwargs,
|
|
@@ -142,7 +182,7 @@ class OpenAIProvider(LLMProvider):
|
|
|
142
182
|
response: OpenAIResponse = await self.client.responses.create(
|
|
143
183
|
model=self._model,
|
|
144
184
|
instructions=state.system_message,
|
|
145
|
-
input=
|
|
185
|
+
input=input_messages,
|
|
146
186
|
tools=[tool.to_openai_spec() for tool in tool_defs],
|
|
147
187
|
**kwargs,
|
|
148
188
|
)
|
|
@@ -8,6 +8,7 @@ compatible with the pyagentic framework.
|
|
|
8
8
|
from typing import Optional, Type
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
10
|
from pydantic import BaseModel
|
|
11
|
+
from PIL.Image import Image
|
|
11
12
|
|
|
12
13
|
from pyagentic._base._tool import _ToolDefinition
|
|
13
14
|
from pyagentic._base._agent._agent_state import _AgentState
|
|
@@ -87,6 +88,9 @@ class LLMProvider(ABC):
|
|
|
87
88
|
"""
|
|
88
89
|
Generate a response from the language model.
|
|
89
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
|
+
|
|
90
94
|
Args:
|
|
91
95
|
state: The agent state containing conversation history and system messages
|
|
92
96
|
tool_defs: Optional list of tool definitions the model can use
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
from typing import Any, List, Optional
|
|
2
|
-
from pydantic import BaseModel
|
|
1
|
+
from typing import Any, List, Optional, TYPE_CHECKING
|
|
2
|
+
from pydantic import BaseModel, ConfigDict
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from PIL.Image import Image
|
|
3
6
|
|
|
4
7
|
|
|
5
8
|
class Message(BaseModel):
|
|
@@ -10,9 +13,12 @@ class Message(BaseModel):
|
|
|
10
13
|
to a dictionary format for API communication.
|
|
11
14
|
"""
|
|
12
15
|
|
|
16
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
17
|
+
|
|
13
18
|
type: Optional[str] = None
|
|
14
19
|
role: Optional[str] = None
|
|
15
20
|
content: Optional[str] = None
|
|
21
|
+
image: Optional["Image"] = None
|
|
16
22
|
|
|
17
23
|
def to_dict(self, exclude_none: bool = True):
|
|
18
24
|
"""
|
|
@@ -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,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyagentic-core
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0a2
|
|
4
4
|
Summary: Build LLM Agents in a Pythonic way
|
|
5
5
|
Author-email: Ryan Mikulec <rmikulec.dev@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -21,6 +21,7 @@ Requires-Dist: c3linearize>=0.1.0
|
|
|
21
21
|
Requires-Dist: anthropic>=0.62.0
|
|
22
22
|
Requires-Dist: google-generativeai>=0.8.0
|
|
23
23
|
Requires-Dist: transitions>=0.9.3
|
|
24
|
+
Requires-Dist: pillow>=12.1.0
|
|
24
25
|
Dynamic: license-file
|
|
25
26
|
|
|
26
27
|
# PyAgentic
|
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyagentic-core"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.0-a.2"
|
|
4
4
|
description = "Build LLM Agents in a Pythonic way"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -24,6 +24,7 @@ dependencies = [
|
|
|
24
24
|
"anthropic>=0.62.0",
|
|
25
25
|
"google-generativeai>=0.8.0",
|
|
26
26
|
"transitions>=0.9.3",
|
|
27
|
+
"pillow>=12.1.0",
|
|
27
28
|
]
|
|
28
29
|
|
|
29
30
|
[dependency-groups]
|
|
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.2.2 → pyagentic_core-2.3.0a2}/pyagentic_core.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|