pyagentic-core 2.2.0a1__tar.gz → 2.2.1__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.0a1 → pyagentic_core-2.2.1}/PKG-INFO +1 -1
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/_agent/_agent.py +35 -35
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/_agent/_agent_state.py +42 -3
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/_info.py +1 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/_spec.py +9 -2
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/_tool.py +5 -4
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic_core.egg-info/PKG-INFO +1 -1
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyproject.toml +1 -1
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/LICENSE +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/README.md +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/__init__.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/__init__.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/_agent/__init__.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/_agent/_agent_linking.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/_exceptions.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/_metaclasses.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/_ref.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/_state.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_base/_validation.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_utils/_typing.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/_utils/_warnings.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/llm/__init__.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/llm/_anthropic.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/llm/_gemini.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/llm/_mock.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/llm/_openai.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/llm/_openaiv1.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/llm/_provider.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/logging.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/models/llm.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/models/response.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/models/tracing.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/policies/__init__.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/policies/_events.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/policies/_policy.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/tracing/__init__.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/tracing/_basic.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/tracing/_langfuse.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/tracing/_tracer.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic/updates.py +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic_core.egg-info/SOURCES.txt +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic_core.egg-info/dependency_links.txt +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic_core.egg-info/requires.txt +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/pyagentic_core.egg-info/top_level.txt +0 -0
- {pyagentic_core-2.2.0a1 → pyagentic_core-2.2.1}/setup.cfg +0 -0
|
@@ -169,7 +169,6 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
169
169
|
phases: ClassVar[list[tuple[str, str, Callable]]] = None
|
|
170
170
|
|
|
171
171
|
# Generated Class Attributes (built by metaclass)
|
|
172
|
-
__machine__: Machine | None = None
|
|
173
172
|
__response_model__: ClassVar[Type[AgentResponse]] = None # Pydantic response model
|
|
174
173
|
__state_class__: ClassVar[Type[_AgentState]] = None # Generated state class
|
|
175
174
|
__tool_response_models__: ClassVar[dict[str, Type[ToolResponse]]] # Tool response models
|
|
@@ -232,34 +231,6 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
232
231
|
if self.__tool_defs__ and not self.provider.__supports_tool_calls__:
|
|
233
232
|
raise Exception("Tools are not supported with this provider")
|
|
234
233
|
|
|
235
|
-
def _build_phase_machine(self) -> Machine:
|
|
236
|
-
if not self.phases:
|
|
237
|
-
return None
|
|
238
|
-
|
|
239
|
-
states = []
|
|
240
|
-
for to_, from_, _ in self.phases:
|
|
241
|
-
if to_ not in states:
|
|
242
|
-
states.append(to_)
|
|
243
|
-
if from_ not in states:
|
|
244
|
-
states.append(from_)
|
|
245
|
-
|
|
246
|
-
machine = Machine(states=states, initial=states[0])
|
|
247
|
-
|
|
248
|
-
for to_, from_, _ in self.phases:
|
|
249
|
-
machine.add_transition(
|
|
250
|
-
trigger=f"{to_}_to_{from_}",
|
|
251
|
-
source=to_,
|
|
252
|
-
dest=from_,
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
return machine
|
|
256
|
-
|
|
257
|
-
def _update_state_machine(self):
|
|
258
|
-
for to_, from_, condition in self.phases:
|
|
259
|
-
if condition(self.state):
|
|
260
|
-
trigger = f"{to_}_to_{from_}"
|
|
261
|
-
getattr(self.__machine__, trigger)()
|
|
262
|
-
|
|
263
234
|
def __post_init__(self):
|
|
264
235
|
"""
|
|
265
236
|
Post-initialization hook called after agent instance is created.
|
|
@@ -281,7 +252,9 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
281
252
|
```
|
|
282
253
|
"""
|
|
283
254
|
self._check_llm_provider()
|
|
284
|
-
|
|
255
|
+
|
|
256
|
+
if self.phases:
|
|
257
|
+
self.state._build_phase_machine(self.phases)
|
|
285
258
|
|
|
286
259
|
# Use BasicTracer as default if no tracer provided
|
|
287
260
|
if not self.tracer:
|
|
@@ -347,7 +320,14 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
347
320
|
self.state._messages.append(
|
|
348
321
|
Message(role="assistant", content="Failed to generate a response")
|
|
349
322
|
)
|
|
350
|
-
|
|
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
|
|
351
331
|
|
|
352
332
|
@traced(SpanKind.AGENT)
|
|
353
333
|
async def _process_agent_call(self, tool_call: ToolCall) -> AgentResponse:
|
|
@@ -394,6 +374,8 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
394
374
|
self.state._messages.append(
|
|
395
375
|
self.provider.to_tool_call_result_message(result=stringified_result, id_=tool_call.id)
|
|
396
376
|
)
|
|
377
|
+
if self.phases:
|
|
378
|
+
self.state._update_state_machine(phases=self.phases)
|
|
397
379
|
return response
|
|
398
380
|
|
|
399
381
|
@traced(SpanKind.TOOL)
|
|
@@ -458,6 +440,8 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
458
440
|
self.provider.to_tool_call_result_message(result=stringified_result, id_=tool_call.id)
|
|
459
441
|
)
|
|
460
442
|
|
|
443
|
+
if self.phases:
|
|
444
|
+
self.state._update_state_machine(phases=self.phases)
|
|
461
445
|
# Build and return the structured tool response
|
|
462
446
|
ToolResponseModel = self.__tool_response_models__[tool_call.name]
|
|
463
447
|
return ToolResponseModel(
|
|
@@ -478,13 +462,30 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
478
462
|
|
|
479
463
|
# Add all @tool decorated methods
|
|
480
464
|
for tool_def in self.__tool_defs__.values():
|
|
465
|
+
if tool_def.condition:
|
|
466
|
+
if not tool_def.condition(self.state):
|
|
467
|
+
continue
|
|
481
468
|
# Resolve StateRefs in parameters (e.g., ref.self.user_name -> actual value)
|
|
482
|
-
|
|
469
|
+
if self.phases and tool_def.phases:
|
|
470
|
+
if self.state.phase in tool_def.phases:
|
|
471
|
+
tool_defs.append(tool_def.resolve(self.agent_reference))
|
|
472
|
+
else:
|
|
473
|
+
tool_defs.append(tool_def.resolve(self.agent_reference))
|
|
483
474
|
|
|
484
475
|
# Add linked agents as tools
|
|
485
476
|
for name, linked_def in self.__linked_agents__.items():
|
|
486
|
-
|
|
487
|
-
|
|
477
|
+
|
|
478
|
+
if linked_def.info.condition:
|
|
479
|
+
if not linked_def.info.condition(self.state):
|
|
480
|
+
continue
|
|
481
|
+
|
|
482
|
+
if self.phases and linked_def.info.phases:
|
|
483
|
+
if self.state.phase in linked_def.info.phases:
|
|
484
|
+
tool_def = linked_def.agent.get_tool_definition(name)
|
|
485
|
+
tool_defs.append(tool_def.resolve(self.agent_reference))
|
|
486
|
+
else:
|
|
487
|
+
tool_def = linked_def.agent.get_tool_definition(name)
|
|
488
|
+
tool_defs.append(tool_def.resolve(self.agent_reference))
|
|
488
489
|
|
|
489
490
|
return tool_defs
|
|
490
491
|
|
|
@@ -579,7 +580,6 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
579
580
|
yield result
|
|
580
581
|
|
|
581
582
|
# Increment depth and continue loop (LLM will see tool results next iteration)
|
|
582
|
-
self._update_state_machine()
|
|
583
583
|
depth += 1
|
|
584
584
|
|
|
585
585
|
# If we exhausted max_call_depth without final text, get one more response
|
|
@@ -1,7 +1,7 @@
|
|
|
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
6
|
from typing import Optional, Literal, Callable
|
|
7
7
|
from transitions import Machine
|
|
@@ -31,12 +31,41 @@ class _AgentState(BaseModel):
|
|
|
31
31
|
|
|
32
32
|
instructions: str
|
|
33
33
|
input_template: Optional[str] = "{{ user_message }}"
|
|
34
|
+
_machine: Machine = PrivateAttr(default=None)
|
|
34
35
|
_messages: list[Message] = PrivateAttr(default_factory=list)
|
|
35
36
|
_instructions_template: Template = PrivateAttr(default_factory=lambda: Template(source=""))
|
|
36
37
|
_input_template: Template = PrivateAttr(
|
|
37
38
|
default_factory=lambda: Template(source="{{ user_message }}")
|
|
38
39
|
)
|
|
39
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
|
+
|
|
40
69
|
def model_post_init(self, state):
|
|
41
70
|
self._instructions_template = Template(source=self.instructions)
|
|
42
71
|
if self.input_template:
|
|
@@ -222,6 +251,10 @@ class _AgentState(BaseModel):
|
|
|
222
251
|
# now build the dataclass
|
|
223
252
|
return create_model(f"AgentState[{name}]", __base__=cls, **pydantic_fields)
|
|
224
253
|
|
|
254
|
+
@property
|
|
255
|
+
def phase(self) -> str:
|
|
256
|
+
return self._machine.state if self._machine else None
|
|
257
|
+
|
|
225
258
|
@property
|
|
226
259
|
def recent_message(self) -> Message:
|
|
227
260
|
"""
|
|
@@ -243,7 +276,10 @@ class _AgentState(BaseModel):
|
|
|
243
276
|
# start with all the normal dataclass fields
|
|
244
277
|
|
|
245
278
|
# now format your instruction template
|
|
246
|
-
|
|
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())
|
|
247
283
|
|
|
248
284
|
@property
|
|
249
285
|
def messages(self) -> list[Message]:
|
|
@@ -271,7 +307,10 @@ class _AgentState(BaseModel):
|
|
|
271
307
|
if self.input_template:
|
|
272
308
|
data = self.model_dump()
|
|
273
309
|
data["user_message"] = message
|
|
274
|
-
|
|
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())
|
|
275
314
|
else:
|
|
276
315
|
content = message
|
|
277
316
|
self._messages.append(Message(role="user", content=content))
|
|
@@ -117,7 +117,10 @@ class spec:
|
|
|
117
117
|
|
|
118
118
|
@staticmethod
|
|
119
119
|
def AgentLink(
|
|
120
|
-
default: Any = None,
|
|
120
|
+
default: Any = None,
|
|
121
|
+
default_factory: Callable = None,
|
|
122
|
+
condition: Callable = None,
|
|
123
|
+
phases: list[str] | None = None,
|
|
121
124
|
) -> AgentInfo:
|
|
122
125
|
"""
|
|
123
126
|
Creates an AgentInfo descriptor for configuring linked agent fields.
|
|
@@ -126,8 +129,12 @@ class spec:
|
|
|
126
129
|
default (Any, optional): The default agent instance
|
|
127
130
|
default_factory (Callable, optional): A factory function to generate the default agent
|
|
128
131
|
condition (Callable, optional): A callable determining when this agent link is active
|
|
132
|
+
phases (list[str], optional): A list of phases of when this agent will be available.
|
|
133
|
+
When None, will show for all phases. Defaults to None.
|
|
129
134
|
|
|
130
135
|
Returns:
|
|
131
136
|
AgentInfo: A configured AgentInfo descriptor
|
|
132
137
|
"""
|
|
133
|
-
return AgentInfo(
|
|
138
|
+
return AgentInfo(
|
|
139
|
+
default=default, default_factory=default_factory, condition=condition, phases=phases
|
|
140
|
+
)
|
|
@@ -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
|
|
|
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.2.0a1 → pyagentic_core-2.2.1}/pyagentic_core.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|