pyagentic-core 2.2.0__tar.gz → 2.2.0a1__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 (45) hide show
  1. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/PKG-INFO +1 -1
  2. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/_agent/_agent.py +33 -21
  3. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/_agent/_agent_state.py +3 -42
  4. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/_tool.py +4 -5
  5. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic_core.egg-info/PKG-INFO +1 -1
  6. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyproject.toml +1 -1
  7. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/LICENSE +0 -0
  8. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/README.md +0 -0
  9. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/__init__.py +0 -0
  10. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/__init__.py +0 -0
  11. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/_agent/__init__.py +0 -0
  12. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/_agent/_agent_linking.py +0 -0
  13. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/_exceptions.py +0 -0
  14. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/_info.py +0 -0
  15. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/_metaclasses.py +0 -0
  16. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/_ref.py +0 -0
  17. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/_spec.py +0 -0
  18. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/_state.py +0 -0
  19. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_base/_validation.py +0 -0
  20. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_utils/_typing.py +0 -0
  21. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/_utils/_warnings.py +0 -0
  22. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/llm/__init__.py +0 -0
  23. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/llm/_anthropic.py +0 -0
  24. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/llm/_gemini.py +0 -0
  25. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/llm/_mock.py +0 -0
  26. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/llm/_openai.py +0 -0
  27. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/llm/_openaiv1.py +0 -0
  28. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/llm/_provider.py +0 -0
  29. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/logging.py +0 -0
  30. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/models/llm.py +0 -0
  31. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/models/response.py +0 -0
  32. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/models/tracing.py +0 -0
  33. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/policies/__init__.py +0 -0
  34. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/policies/_events.py +0 -0
  35. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/policies/_policy.py +0 -0
  36. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/tracing/__init__.py +0 -0
  37. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/tracing/_basic.py +0 -0
  38. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/tracing/_langfuse.py +0 -0
  39. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/tracing/_tracer.py +0 -0
  40. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic/updates.py +0 -0
  41. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic_core.egg-info/SOURCES.txt +0 -0
  42. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic_core.egg-info/dependency_links.txt +0 -0
  43. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic_core.egg-info/requires.txt +0 -0
  44. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/pyagentic_core.egg-info/top_level.txt +0 -0
  45. {pyagentic_core-2.2.0 → pyagentic_core-2.2.0a1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyagentic-core
3
- Version: 2.2.0
3
+ Version: 2.2.0a1
4
4
  Summary: Build LLM Agents in a Pythonic way
5
5
  Author-email: Ryan Mikulec <rmikulec.dev@gmail.com>
6
6
  License: MIT
@@ -169,6 +169,7 @@ 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
172
173
  __response_model__: ClassVar[Type[AgentResponse]] = None # Pydantic response model
173
174
  __state_class__: ClassVar[Type[_AgentState]] = None # Generated state class
174
175
  __tool_response_models__: ClassVar[dict[str, Type[ToolResponse]]] # Tool response models
@@ -231,6 +232,34 @@ class BaseAgent(metaclass=AgentMeta):
231
232
  if self.__tool_defs__ and not self.provider.__supports_tool_calls__:
232
233
  raise Exception("Tools are not supported with this provider")
233
234
 
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
+
234
263
  def __post_init__(self):
235
264
  """
236
265
  Post-initialization hook called after agent instance is created.
@@ -252,9 +281,7 @@ class BaseAgent(metaclass=AgentMeta):
252
281
  ```
253
282
  """
254
283
  self._check_llm_provider()
255
-
256
- if self.phases:
257
- self.state._build_phase_machine(self.phases)
284
+ self.__machine__ = self._build_phase_machine()
258
285
 
259
286
  # Use BasicTracer as default if no tracer provided
260
287
  if not self.tracer:
@@ -320,14 +347,7 @@ class BaseAgent(metaclass=AgentMeta):
320
347
  self.state._messages.append(
321
348
  Message(role="assistant", content="Failed to generate a response")
322
349
  )
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
350
+ return LLMResponse(text=f"The LLM failed to generate a response: {e}", tool_calls=[])
331
351
 
332
352
  @traced(SpanKind.AGENT)
333
353
  async def _process_agent_call(self, tool_call: ToolCall) -> AgentResponse:
@@ -374,8 +394,6 @@ class BaseAgent(metaclass=AgentMeta):
374
394
  self.state._messages.append(
375
395
  self.provider.to_tool_call_result_message(result=stringified_result, id_=tool_call.id)
376
396
  )
377
- if self.phases:
378
- self.state._update_state_machine(phases=self.phases)
379
397
  return response
380
398
 
381
399
  @traced(SpanKind.TOOL)
@@ -440,8 +458,6 @@ class BaseAgent(metaclass=AgentMeta):
440
458
  self.provider.to_tool_call_result_message(result=stringified_result, id_=tool_call.id)
441
459
  )
442
460
 
443
- if self.phases:
444
- self.state._update_state_machine(phases=self.phases)
445
461
  # Build and return the structured tool response
446
462
  ToolResponseModel = self.__tool_response_models__[tool_call.name]
447
463
  return ToolResponseModel(
@@ -463,12 +479,7 @@ class BaseAgent(metaclass=AgentMeta):
463
479
  # Add all @tool decorated methods
464
480
  for tool_def in self.__tool_defs__.values():
465
481
  # Resolve StateRefs in parameters (e.g., ref.self.user_name -> actual value)
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))
482
+ tool_defs.append(tool_def.resolve(self.agent_reference))
472
483
 
473
484
  # Add linked agents as tools
474
485
  for name, linked_def in self.__linked_agents__.items():
@@ -568,6 +579,7 @@ class BaseAgent(metaclass=AgentMeta):
568
579
  yield result
569
580
 
570
581
  # Increment depth and continue loop (LLM will see tool results next iteration)
582
+ self._update_state_machine()
571
583
  depth += 1
572
584
 
573
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, computed_field
4
+ from pydantic import BaseModel, create_model, Field, PrivateAttr, ConfigDict
5
5
  from jinja2 import Template
6
6
  from typing import Optional, Literal, Callable
7
7
  from transitions import Machine
@@ -31,41 +31,12 @@ class _AgentState(BaseModel):
31
31
 
32
32
  instructions: str
33
33
  input_template: Optional[str] = "{{ user_message }}"
34
- _machine: Machine = PrivateAttr(default=None)
35
34
  _messages: list[Message] = PrivateAttr(default_factory=list)
36
35
  _instructions_template: Template = PrivateAttr(default_factory=lambda: Template(source=""))
37
36
  _input_template: Template = PrivateAttr(
38
37
  default_factory=lambda: Template(source="{{ user_message }}")
39
38
  )
40
39
 
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
-
69
40
  def model_post_init(self, state):
70
41
  self._instructions_template = Template(source=self.instructions)
71
42
  if self.input_template:
@@ -251,10 +222,6 @@ class _AgentState(BaseModel):
251
222
  # now build the dataclass
252
223
  return create_model(f"AgentState[{name}]", __base__=cls, **pydantic_fields)
253
224
 
254
- @property
255
- def phase(self) -> str:
256
- return self._machine.state if self._machine else None
257
-
258
225
  @property
259
226
  def recent_message(self) -> Message:
260
227
  """
@@ -276,10 +243,7 @@ class _AgentState(BaseModel):
276
243
  # start with all the normal dataclass fields
277
244
 
278
245
  # now format your instruction template
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
+ return self._instructions_template.render(**self.model_dump())
283
247
 
284
248
  @property
285
249
  def messages(self) -> list[Message]:
@@ -307,10 +271,7 @@ class _AgentState(BaseModel):
307
271
  if self.input_template:
308
272
  data = self.model_dump()
309
273
  data["user_message"] = message
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
+ content = self._input_template.render(**data)
314
275
  else:
315
276
  content = message
316
277
  self._messages.append(Message(role="user", content=content))
@@ -43,14 +43,12 @@ 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,
47
46
  ):
48
47
  self.name: str = name
49
48
  self.description: str = description
50
49
  self.parameters: dict[str, tuple[TypeVar, ParamInfo]] = parameters
51
50
  self.condition = condition
52
51
  self.return_type = return_type
53
- self.phases = phases if phases else []
54
52
 
55
53
  def resolve(self, agent_reference: dict) -> Self:
56
54
  new_parameters = {}
@@ -69,7 +67,6 @@ class _ToolDefinition:
69
67
  parameters=new_parameters,
70
68
  condition=self.condition,
71
69
  return_type=self.return_type,
72
- phases=self.phases,
73
70
  )
74
71
 
75
72
  def to_openai_spec(self) -> dict:
@@ -213,7 +210,10 @@ class _ToolDefinition:
213
210
  return compiled_args
214
211
 
215
212
 
216
- def tool(description: str, condition: Callable[[Any], bool] = None, phases: list[str] = None):
213
+ def tool(
214
+ description: str,
215
+ condition: Callable[[Any], bool] = None,
216
+ ):
217
217
  """
218
218
  Decorator to mark an agent method as a tool that the LLM can call.
219
219
 
@@ -281,7 +281,6 @@ def tool(description: str, condition: Callable[[Any], bool] = None, phases: list
281
281
  parameters=params,
282
282
  condition=condition,
283
283
  return_type=return_type,
284
- phases=phases,
285
284
  )
286
285
  return fn
287
286
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyagentic-core
3
- Version: 2.2.0
3
+ Version: 2.2.0a1
4
4
  Summary: Build LLM Agents in a Pythonic way
5
5
  Author-email: Ryan Mikulec <rmikulec.dev@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyagentic-core"
3
- version = "2.2.0"
3
+ version = "2.2.0-a.1"
4
4
  description = "Build LLM Agents in a Pythonic way"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
File without changes