pyagentic-core 2.1.0a3__tar.gz → 2.2.0__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.1.0a3 → pyagentic_core-2.2.0}/PKG-INFO +2 -1
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/_agent/_agent.py +23 -2
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/_agent/_agent_state.py +44 -4
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/_tool.py +5 -4
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic_core.egg-info/PKG-INFO +2 -1
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic_core.egg-info/requires.txt +1 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyproject.toml +3 -2
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/LICENSE +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/README.md +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/__init__.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/__init__.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/_agent/__init__.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/_agent/_agent_linking.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/_exceptions.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/_info.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/_metaclasses.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/_ref.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/_spec.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/_state.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_base/_validation.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_utils/_typing.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/_utils/_warnings.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/llm/__init__.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/llm/_anthropic.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/llm/_gemini.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/llm/_mock.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/llm/_openai.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/llm/_openaiv1.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/llm/_provider.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/logging.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/models/llm.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/models/response.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/models/tracing.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/policies/__init__.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/policies/_events.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/policies/_policy.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/tracing/__init__.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/tracing/_basic.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/tracing/_langfuse.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/tracing/_tracer.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic/updates.py +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic_core.egg-info/SOURCES.txt +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic_core.egg-info/dependency_links.txt +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic_core.egg-info/top_level.txt +0 -0
- {pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/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.2.0
|
|
4
4
|
Summary: Build LLM Agents in a Pythonic way
|
|
5
5
|
Author-email: Ryan Mikulec <rmikulec.dev@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -20,6 +20,7 @@ Requires-Dist: typeguard>=4.4.4
|
|
|
20
20
|
Requires-Dist: c3linearize>=0.1.0
|
|
21
21
|
Requires-Dist: anthropic>=0.62.0
|
|
22
22
|
Requires-Dist: google-generativeai>=0.8.0
|
|
23
|
+
Requires-Dist: transitions>=0.9.3
|
|
23
24
|
Dynamic: license-file
|
|
24
25
|
|
|
25
26
|
# PyAgentic
|
|
@@ -13,6 +13,7 @@ from typing import (
|
|
|
13
13
|
Union,
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
+
from transitions import Machine
|
|
16
17
|
from pydantic import BaseModel, ValidationError
|
|
17
18
|
|
|
18
19
|
from pyagentic.logging import get_logger
|
|
@@ -165,6 +166,7 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
165
166
|
__description__: ClassVar[str] # Optional: description for linked agents
|
|
166
167
|
__input_template__: ClassVar[str] = None # Optional: template for user input
|
|
167
168
|
__response_format__: ClassVar[Type[BaseModel]] = None # Optional: structured output format
|
|
169
|
+
phases: ClassVar[list[tuple[str, str, Callable]]] = None
|
|
168
170
|
|
|
169
171
|
# Generated Class Attributes (built by metaclass)
|
|
170
172
|
__response_model__: ClassVar[Type[AgentResponse]] = None # Pydantic response model
|
|
@@ -251,6 +253,9 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
251
253
|
"""
|
|
252
254
|
self._check_llm_provider()
|
|
253
255
|
|
|
256
|
+
if self.phases:
|
|
257
|
+
self.state._build_phase_machine(self.phases)
|
|
258
|
+
|
|
254
259
|
# Use BasicTracer as default if no tracer provided
|
|
255
260
|
if not self.tracer:
|
|
256
261
|
self.tracer = BasicTracer()
|
|
@@ -315,7 +320,14 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
315
320
|
self.state._messages.append(
|
|
316
321
|
Message(role="assistant", content="Failed to generate a response")
|
|
317
322
|
)
|
|
318
|
-
|
|
323
|
+
response = LLMResponse(
|
|
324
|
+
text=f"The LLM failed to generate a response: {e}", tool_calls=[]
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
if self.phases:
|
|
328
|
+
self.state._update_state_machine(phases=self.phases)
|
|
329
|
+
|
|
330
|
+
return response
|
|
319
331
|
|
|
320
332
|
@traced(SpanKind.AGENT)
|
|
321
333
|
async def _process_agent_call(self, tool_call: ToolCall) -> AgentResponse:
|
|
@@ -362,6 +374,8 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
362
374
|
self.state._messages.append(
|
|
363
375
|
self.provider.to_tool_call_result_message(result=stringified_result, id_=tool_call.id)
|
|
364
376
|
)
|
|
377
|
+
if self.phases:
|
|
378
|
+
self.state._update_state_machine(phases=self.phases)
|
|
365
379
|
return response
|
|
366
380
|
|
|
367
381
|
@traced(SpanKind.TOOL)
|
|
@@ -426,6 +440,8 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
426
440
|
self.provider.to_tool_call_result_message(result=stringified_result, id_=tool_call.id)
|
|
427
441
|
)
|
|
428
442
|
|
|
443
|
+
if self.phases:
|
|
444
|
+
self.state._update_state_machine(phases=self.phases)
|
|
429
445
|
# Build and return the structured tool response
|
|
430
446
|
ToolResponseModel = self.__tool_response_models__[tool_call.name]
|
|
431
447
|
return ToolResponseModel(
|
|
@@ -447,7 +463,12 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
447
463
|
# Add all @tool decorated methods
|
|
448
464
|
for tool_def in self.__tool_defs__.values():
|
|
449
465
|
# Resolve StateRefs in parameters (e.g., ref.self.user_name -> actual value)
|
|
450
|
-
|
|
466
|
+
|
|
467
|
+
if self.phases and tool_def.phases:
|
|
468
|
+
if self.state.phase in tool_def.phases:
|
|
469
|
+
tool_defs.append(tool_def.resolve(self.agent_reference))
|
|
470
|
+
else:
|
|
471
|
+
tool_defs.append(tool_def.resolve(self.agent_reference))
|
|
451
472
|
|
|
452
473
|
# Add linked agents as tools
|
|
453
474
|
for name, linked_def in self.__linked_agents__.items():
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import threading
|
|
3
3
|
from typing import Any, Type, Self, Optional, ClassVar
|
|
4
|
-
from pydantic import BaseModel, create_model, Field, PrivateAttr
|
|
4
|
+
from pydantic import BaseModel, create_model, Field, PrivateAttr, computed_field
|
|
5
5
|
from jinja2 import Template
|
|
6
|
-
from typing import Optional, Literal
|
|
6
|
+
from typing import Optional, Literal, Callable
|
|
7
|
+
from transitions import Machine
|
|
7
8
|
|
|
8
9
|
from pyagentic._base._exceptions import InvalidStateRefNotFoundInState
|
|
9
10
|
from pyagentic._base._state import _StateDefinition
|
|
@@ -30,12 +31,41 @@ class _AgentState(BaseModel):
|
|
|
30
31
|
|
|
31
32
|
instructions: str
|
|
32
33
|
input_template: Optional[str] = "{{ user_message }}"
|
|
34
|
+
_machine: Machine = PrivateAttr(default=None)
|
|
33
35
|
_messages: list[Message] = PrivateAttr(default_factory=list)
|
|
34
36
|
_instructions_template: Template = PrivateAttr(default_factory=lambda: Template(source=""))
|
|
35
37
|
_input_template: Template = PrivateAttr(
|
|
36
38
|
default_factory=lambda: Template(source="{{ user_message }}")
|
|
37
39
|
)
|
|
38
40
|
|
|
41
|
+
def _build_phase_machine(self, phases: list[tuple[str, str, Callable]]) -> Machine:
|
|
42
|
+
if not phases:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
states = []
|
|
46
|
+
for source, dest, _ in phases:
|
|
47
|
+
if source not in states:
|
|
48
|
+
states.append(source)
|
|
49
|
+
if dest not in states:
|
|
50
|
+
states.append(dest)
|
|
51
|
+
|
|
52
|
+
machine = Machine(states=states, initial=states[0])
|
|
53
|
+
|
|
54
|
+
for source, dest, _ in phases:
|
|
55
|
+
machine.add_transition(
|
|
56
|
+
trigger=f"{source}_to_{dest}",
|
|
57
|
+
source=source,
|
|
58
|
+
dest=dest,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self._machine = machine
|
|
62
|
+
|
|
63
|
+
def _update_state_machine(self, phases):
|
|
64
|
+
for to_, from_, condition in phases:
|
|
65
|
+
if condition(self):
|
|
66
|
+
trigger = f"{to_}_to_{from_}"
|
|
67
|
+
getattr(self._machine, trigger)()
|
|
68
|
+
|
|
39
69
|
def model_post_init(self, state):
|
|
40
70
|
self._instructions_template = Template(source=self.instructions)
|
|
41
71
|
if self.input_template:
|
|
@@ -221,6 +251,10 @@ class _AgentState(BaseModel):
|
|
|
221
251
|
# now build the dataclass
|
|
222
252
|
return create_model(f"AgentState[{name}]", __base__=cls, **pydantic_fields)
|
|
223
253
|
|
|
254
|
+
@property
|
|
255
|
+
def phase(self) -> str:
|
|
256
|
+
return self._machine.state if self._machine else None
|
|
257
|
+
|
|
224
258
|
@property
|
|
225
259
|
def recent_message(self) -> Message:
|
|
226
260
|
"""
|
|
@@ -242,7 +276,10 @@ class _AgentState(BaseModel):
|
|
|
242
276
|
# start with all the normal dataclass fields
|
|
243
277
|
|
|
244
278
|
# now format your instruction template
|
|
245
|
-
|
|
279
|
+
if self.phase:
|
|
280
|
+
return self._instructions_template.render(phase=self.phase, **self.model_dump())
|
|
281
|
+
else:
|
|
282
|
+
return self._instructions_template.render(**self.model_dump())
|
|
246
283
|
|
|
247
284
|
@property
|
|
248
285
|
def messages(self) -> list[Message]:
|
|
@@ -270,7 +307,10 @@ class _AgentState(BaseModel):
|
|
|
270
307
|
if self.input_template:
|
|
271
308
|
data = self.model_dump()
|
|
272
309
|
data["user_message"] = message
|
|
273
|
-
|
|
310
|
+
if self.phase:
|
|
311
|
+
content = self._input_template.render(phase=self.phase, **self.model_dump())
|
|
312
|
+
else:
|
|
313
|
+
content = self._input_template.render(**self.model_dump())
|
|
274
314
|
else:
|
|
275
315
|
content = message
|
|
276
316
|
self._messages.append(Message(role="user", content=content))
|
|
@@ -43,12 +43,14 @@ class _ToolDefinition:
|
|
|
43
43
|
parameters: dict[str, tuple[TypeVar, ParamInfo]],
|
|
44
44
|
return_type: Type[Any],
|
|
45
45
|
condition: Callable[[Any], bool] = None,
|
|
46
|
+
phases: list[str] = None,
|
|
46
47
|
):
|
|
47
48
|
self.name: str = name
|
|
48
49
|
self.description: str = description
|
|
49
50
|
self.parameters: dict[str, tuple[TypeVar, ParamInfo]] = parameters
|
|
50
51
|
self.condition = condition
|
|
51
52
|
self.return_type = return_type
|
|
53
|
+
self.phases = phases if phases else []
|
|
52
54
|
|
|
53
55
|
def resolve(self, agent_reference: dict) -> Self:
|
|
54
56
|
new_parameters = {}
|
|
@@ -67,6 +69,7 @@ class _ToolDefinition:
|
|
|
67
69
|
parameters=new_parameters,
|
|
68
70
|
condition=self.condition,
|
|
69
71
|
return_type=self.return_type,
|
|
72
|
+
phases=self.phases,
|
|
70
73
|
)
|
|
71
74
|
|
|
72
75
|
def to_openai_spec(self) -> dict:
|
|
@@ -210,10 +213,7 @@ class _ToolDefinition:
|
|
|
210
213
|
return compiled_args
|
|
211
214
|
|
|
212
215
|
|
|
213
|
-
def tool(
|
|
214
|
-
description: str,
|
|
215
|
-
condition: Callable[[Any], bool] = None,
|
|
216
|
-
):
|
|
216
|
+
def tool(description: str, condition: Callable[[Any], bool] = None, phases: list[str] = None):
|
|
217
217
|
"""
|
|
218
218
|
Decorator to mark an agent method as a tool that the LLM can call.
|
|
219
219
|
|
|
@@ -281,6 +281,7 @@ def tool(
|
|
|
281
281
|
parameters=params,
|
|
282
282
|
condition=condition,
|
|
283
283
|
return_type=return_type,
|
|
284
|
+
phases=phases,
|
|
284
285
|
)
|
|
285
286
|
return fn
|
|
286
287
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyagentic-core
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Build LLM Agents in a Pythonic way
|
|
5
5
|
Author-email: Ryan Mikulec <rmikulec.dev@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -20,6 +20,7 @@ Requires-Dist: typeguard>=4.4.4
|
|
|
20
20
|
Requires-Dist: c3linearize>=0.1.0
|
|
21
21
|
Requires-Dist: anthropic>=0.62.0
|
|
22
22
|
Requires-Dist: google-generativeai>=0.8.0
|
|
23
|
+
Requires-Dist: transitions>=0.9.3
|
|
23
24
|
Dynamic: license-file
|
|
24
25
|
|
|
25
26
|
# PyAgentic
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyagentic-core"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.2.0"
|
|
4
4
|
description = "Build LLM Agents in a Pythonic way"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -23,6 +23,7 @@ dependencies = [
|
|
|
23
23
|
"c3linearize>=0.1.0",
|
|
24
24
|
"anthropic>=0.62.0",
|
|
25
25
|
"google-generativeai>=0.8.0",
|
|
26
|
+
"transitions>=0.9.3",
|
|
26
27
|
]
|
|
27
28
|
|
|
28
29
|
[dependency-groups]
|
|
@@ -105,4 +106,4 @@ include = ["pyagentic*"]
|
|
|
105
106
|
compile-diagrams = "for file in docs/diagrams/source/*.d2; do d2 --layout elk \"$file\" \"docs/diagrams/$(basename \"$file\" .d2).svg\"; done"
|
|
106
107
|
build-docs = "task compile-diagrams && mkdocs build --clean"
|
|
107
108
|
serve-docs = "task compile-diagrams && mkdocs serve"
|
|
108
|
-
deploy-docs = "task compile-diagrams && mkdocs gh-deploy --force"
|
|
109
|
+
deploy-docs = "task compile-diagrams && mkdocs gh-deploy --force"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyagentic_core-2.1.0a3 → pyagentic_core-2.2.0}/pyagentic_core.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|