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.
Files changed (46) hide show
  1. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/PKG-INFO +2 -1
  2. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_agent/_agent.py +22 -9
  3. pyagentic_core-2.3.0a2/pyagentic/_utils/_image.py +20 -0
  4. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/_openai.py +42 -2
  5. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/_provider.py +4 -0
  6. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/models/llm.py +8 -2
  7. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/models/response.py +17 -1
  8. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic_core.egg-info/PKG-INFO +2 -1
  9. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic_core.egg-info/SOURCES.txt +1 -0
  10. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic_core.egg-info/requires.txt +1 -0
  11. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyproject.toml +2 -1
  12. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/LICENSE +0 -0
  13. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/README.md +0 -0
  14. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/__init__.py +0 -0
  15. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/__init__.py +0 -0
  16. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_agent/__init__.py +0 -0
  17. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_agent/_agent_linking.py +0 -0
  18. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_agent/_agent_state.py +0 -0
  19. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_exceptions.py +0 -0
  20. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_info.py +0 -0
  21. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_metaclasses.py +0 -0
  22. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_ref.py +0 -0
  23. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_spec.py +0 -0
  24. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_state.py +0 -0
  25. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_tool.py +0 -0
  26. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_base/_validation.py +0 -0
  27. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_utils/_typing.py +0 -0
  28. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/_utils/_warnings.py +0 -0
  29. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/__init__.py +0 -0
  30. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/_anthropic.py +0 -0
  31. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/_gemini.py +0 -0
  32. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/_mock.py +0 -0
  33. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/llm/_openaiv1.py +0 -0
  34. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/logging.py +0 -0
  35. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/models/tracing.py +0 -0
  36. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/policies/__init__.py +0 -0
  37. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/policies/_events.py +0 -0
  38. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/policies/_policy.py +0 -0
  39. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/tracing/__init__.py +0 -0
  40. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/tracing/_basic.py +0 -0
  41. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/tracing/_langfuse.py +0 -0
  42. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/tracing/_tracer.py +0 -0
  43. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic/updates.py +0 -0
  44. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic_core.egg-info/dependency_links.txt +0 -0
  45. {pyagentic_core-2.2.2 → pyagentic_core-2.3.0a2}/pyagentic_core.egg-info/top_level.txt +0 -0
  46. {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.2.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
- stringified_result = (
435
- result.model_dump_json(indent=2)
436
- if issubclass(result.__class__, BaseModel)
437
- else str(result)
438
- )
439
- # Add tool result to conversation history for LLM
440
- self.state._messages.append(
441
- self.provider.to_tool_call_result_message(result=stringified_result, id_=tool_call.id)
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=[message.to_dict() for message in state._messages],
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=[message.to_dict() for message in state._messages],
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.2.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
@@ -9,3 +9,4 @@ c3linearize>=0.1.0
9
9
  anthropic>=0.62.0
10
10
  google-generativeai>=0.8.0
11
11
  transitions>=0.9.3
12
+ pillow>=12.1.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyagentic-core"
3
- version = "2.2.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