pyagentic-core 2.1.0a2__tar.gz → 2.1.0a3__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.1.0a2 → pyagentic_core-2.1.0a3}/PKG-INFO +1 -1
  2. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/_agent/_agent.py +92 -58
  3. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic_core.egg-info/PKG-INFO +1 -1
  4. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyproject.toml +1 -1
  5. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/LICENSE +0 -0
  6. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/README.md +0 -0
  7. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/__init__.py +0 -0
  8. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/__init__.py +0 -0
  9. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/_agent/__init__.py +0 -0
  10. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/_agent/_agent_linking.py +0 -0
  11. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/_agent/_agent_state.py +0 -0
  12. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/_exceptions.py +0 -0
  13. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/_info.py +0 -0
  14. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/_metaclasses.py +0 -0
  15. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/_ref.py +0 -0
  16. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/_spec.py +0 -0
  17. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/_state.py +0 -0
  18. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/_tool.py +0 -0
  19. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_base/_validation.py +0 -0
  20. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_utils/_typing.py +0 -0
  21. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/_utils/_warnings.py +0 -0
  22. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/llm/__init__.py +0 -0
  23. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/llm/_anthropic.py +0 -0
  24. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/llm/_gemini.py +0 -0
  25. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/llm/_mock.py +0 -0
  26. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/llm/_openai.py +0 -0
  27. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/llm/_openaiv1.py +0 -0
  28. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/llm/_provider.py +0 -0
  29. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/logging.py +0 -0
  30. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/models/llm.py +0 -0
  31. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/models/response.py +0 -0
  32. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/models/tracing.py +0 -0
  33. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/policies/__init__.py +0 -0
  34. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/policies/_events.py +0 -0
  35. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/policies/_policy.py +0 -0
  36. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/tracing/__init__.py +0 -0
  37. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/tracing/_basic.py +0 -0
  38. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/tracing/_langfuse.py +0 -0
  39. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/tracing/_tracer.py +0 -0
  40. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic/updates.py +0 -0
  41. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic_core.egg-info/SOURCES.txt +0 -0
  42. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic_core.egg-info/dependency_links.txt +0 -0
  43. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic_core.egg-info/requires.txt +0 -0
  44. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/pyagentic_core.egg-info/top_level.txt +0 -0
  45. {pyagentic_core-2.1.0a2 → pyagentic_core-2.1.0a3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyagentic-core
3
- Version: 2.1.0a2
3
+ Version: 2.1.0a3
4
4
  Summary: Build LLM Agents in a Pythonic way
5
5
  Author-email: Ryan Mikulec <rmikulec.dev@gmail.com>
6
6
  License: MIT
@@ -126,8 +126,6 @@ class BaseAgent(metaclass=AgentMeta):
126
126
  api_key (str, optional): API key matching the model provider
127
127
  provider (LLMProvider, optional): Pre-configured provider instance. Overrides
128
128
  `model` and `api_key` if provided.
129
- emitter (Callable, optional): Callback function to receive real-time updates
130
- about the agent's execution (useful for WebSocket streaming)
131
129
  tracer (AgentTracer, optional): Tracer instance for observability. Defaults
132
130
  to BasicTracer if not provided.
133
131
  max_call_depth (int): Maximum number of tool calling loops per run. Defaults to 1.
@@ -177,7 +175,6 @@ class BaseAgent(metaclass=AgentMeta):
177
175
  model: str = None
178
176
  api_key: str = None
179
177
  provider: LLMProvider = None
180
- emitter: Callable[[Any], str] = None
181
178
  tracer: AgentTracer = None
182
179
  max_call_depth: int = 1
183
180
 
@@ -314,8 +311,6 @@ class BaseAgent(metaclass=AgentMeta):
314
311
  except Exception as e:
315
312
  # Handle inference errors gracefully
316
313
  logger.exception(e)
317
- if self.emitter:
318
- await _safe_run(self.emitter, EmitUpdate(status=Status.ERROR))
319
314
  # Add error message to conversation history
320
315
  self.state._messages.append(
321
316
  Message(role="assistant", content="Failed to generate a response")
@@ -402,37 +397,15 @@ class BaseAgent(metaclass=AgentMeta):
402
397
  # Handle validation errors for tool arguments
403
398
  result = f"Function Args were invalid: {str(e)}"
404
399
  compiled_args = {}
405
- if self.emitter:
406
- self.tracer.record_exception(str(e))
407
- logger.exception(e)
408
- if self.emitter:
409
- await _safe_run(
410
- self.emitter,
411
- ToolUpdate(
412
- status=Status.ERROR, tool_call=tool_call.name, tool_args=kwargs
413
- ),
414
- )
415
-
416
- # Execute the tool, emitting status updates
400
+ self.tracer.record_exception(str(e))
401
+ logger.exception(e)
417
402
  try:
418
- if self.emitter:
419
- await _safe_run(
420
- self.emitter,
421
- ToolUpdate(
422
- status=Status.PROCESSING, tool_call=tool_call.name, tool_args=kwargs
423
- ),
424
- )
425
403
  if compiled_args:
426
404
  result = await _safe_run(handler, **compiled_args)
427
405
  self.tracer.set_attributes(result=result)
428
406
  except TypeError as e:
429
407
  self.tracer.record_exception(str(e))
430
408
  logger.exception(e)
431
- if self.emitter:
432
- await _safe_run(
433
- self.emitter,
434
- ToolUpdate(status=Status.ERROR, tool_call=tool_call.name, tool_args=kwargs),
435
- )
436
409
  raise InvalidToolDefinition(
437
410
  tool_name=tool_call.name,
438
411
  message=f"Tool must have a serializable return type; {tool_def.return_type} failed to be casted to a string.",
@@ -442,11 +415,6 @@ class BaseAgent(metaclass=AgentMeta):
442
415
  self.tracer.record_exception(str(e))
443
416
  logger.exception(e)
444
417
  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
445
- if self.emitter:
446
- await _safe_run(
447
- self.emitter,
448
- ToolUpdate(status=Status.ERROR, tool_call=tool_call.name, tool_args=kwargs),
449
- )
450
418
 
451
419
  stringified_result = (
452
420
  result.model_dump_json(indent=2)
@@ -492,10 +460,12 @@ class BaseAgent(metaclass=AgentMeta):
492
460
  self, input_: str
493
461
  ) -> AsyncGenerator[Union[ToolResponse, AgentResponse, LLMResponse]]:
494
462
  """
495
- Main execution loop for the agent. Processes user input through multiple rounds
496
- of LLM inference and tool/agent calls until completion or max_call_depth reached.
463
+ Streams all intermediate responses as the agent executes. Yields LLMResponse for each
464
+ inference, ToolResponse for each tool execution, and finally AgentResponse with the
465
+ complete result.
497
466
 
498
- The agent follows an agentic loop pattern:
467
+ This is the core execution method that enables real-time streaming and fine-grained
468
+ control over agent execution. The agent follows an agentic loop pattern:
499
469
  1. Send user input and conversation history to the LLM
500
470
  2. LLM decides to either call tools or respond with final output
501
471
  3. If tools are called, execute them and feed results back to LLM
@@ -504,19 +474,23 @@ class BaseAgent(metaclass=AgentMeta):
504
474
  Args:
505
475
  input_ (str): The user input/query for the agent to process
506
476
 
507
- Returns:
508
- AgentResponse: Structured response containing:
509
- - final_output: The final text or structured output from the LLM
510
- - state: Current agent state after execution
511
- - tool_responses: List of all tool calls and their outputs
512
- - provider_info: Information about the LLM provider used
477
+ Yields:
478
+ Union[LLMResponse, ToolResponse, AgentResponse]: Responses in sequence:
479
+ - LLMResponse: Yielded each time the LLM is called (may happen multiple times)
480
+ - ToolResponse: Yielded for each tool execution
481
+ - AgentResponse: Final response with complete execution summary
513
482
 
514
483
  Example:
515
484
  ```python
516
485
  agent = MyAgent(model="openai::gpt-4o", api_key=API_KEY)
517
- response = await agent.run("What's the weather in San Francisco?")
518
- print(response.final_output) # LLM's final answer
519
- print(response.tool_responses) # Tools that were called
486
+
487
+ async for response in agent.step("Analyze this data"):
488
+ if isinstance(response, LLMResponse):
489
+ print(f"LLM thinking: {response.text}")
490
+ elif isinstance(response, ToolResponse):
491
+ print(f"Tool executed: {response.output}")
492
+ elif isinstance(response, AgentResponse):
493
+ print(f"Final: {response.final_output}")
520
494
  ```
521
495
  """
522
496
  async with self.tracer.agent(
@@ -538,10 +512,6 @@ class BaseAgent(metaclass=AgentMeta):
538
512
  agent_responses: list = []
539
513
  processed_call_ids: set[str] = set()
540
514
 
541
- # Emit initial status
542
- if self.emitter:
543
- await _safe_run(self.emitter, EmitUpdate(status=Status.GENERATING))
544
-
545
515
  # Main agentic loop: LLM -> Tools -> LLM -> ...
546
516
  depth = 0
547
517
  final_ai_output: str | None = None
@@ -584,12 +554,6 @@ class BaseAgent(metaclass=AgentMeta):
584
554
  response = await self._process_llm_inference()
585
555
  final_ai_output = response.parsed if response.parsed else response.text
586
556
 
587
- # Emit final success status
588
- if self.emitter:
589
- await _safe_run(
590
- self.emitter, AiUpdate(status=Status.SUCCEDED, message=final_ai_output)
591
- )
592
-
593
557
  # Build the structured response
594
558
  response_fields = {
595
559
  "final_output": final_ai_output,
@@ -607,6 +571,32 @@ class BaseAgent(metaclass=AgentMeta):
607
571
  yield response
608
572
 
609
573
  async def run(self, input_: str) -> AgentResponse:
574
+ """
575
+ Executes the agent with a message string and returns the final result.
576
+
577
+ This method consumes the entire step() generator and returns only the final
578
+ AgentResponse. Use this when you don't need intermediate streaming responses
579
+ and just want the final output.
580
+
581
+ Args:
582
+ input_ (str): The user input/query for the agent to process
583
+
584
+ Returns:
585
+ AgentResponse: Structured response containing:
586
+ - final_output: The final text or structured output from the LLM
587
+ - state: Current agent state after execution
588
+ - tool_responses: List of all tool calls and their outputs
589
+ - agent_responses: List of linked agent calls (if any)
590
+ - provider_info: Information about the LLM provider used
591
+
592
+ Example:
593
+ ```python
594
+ agent = MyAgent(model="openai::gpt-4o", api_key=API_KEY)
595
+ response = await agent.run("What's the weather in San Francisco?")
596
+ print(response.final_output) # LLM's final answer
597
+ print(response.tool_responses) # Tools that were called
598
+ ```
599
+ """
610
600
  final_response = None
611
601
  async for res in self.step(input_):
612
602
  final_response = res
@@ -614,13 +604,57 @@ class BaseAgent(metaclass=AgentMeta):
614
604
 
615
605
  async def __call__(self, user_input: str) -> BaseModel:
616
606
  """
617
- Allows the agent to be called directly as a function.
607
+ Customizable callable interface for the agent. Override this method to accept
608
+ structured, typed parameters that match your agent's purpose.
609
+
610
+ When this agent is linked to another agent, the parameters of this method become
611
+ the tool parameters that the LLM sees. This enables type-safe, structured agent
612
+ composition in multi-agent systems.
613
+
614
+ The default implementation accepts a single user_input string and forwards it to
615
+ run(). Override to provide a custom interface:
618
616
 
619
617
  Args:
620
- user_input (str): The user input to process
618
+ user_input (str): The user input to process (default implementation)
621
619
 
622
620
  Returns:
623
621
  AgentResponse: The agent's response
622
+
623
+ Example (Default Usage):
624
+ ```python
625
+ agent = MyAgent(model="openai::gpt-4o", api_key=API_KEY)
626
+ response = await agent("What's the weather?")
627
+ ```
628
+
629
+ Example (Custom Implementation):
630
+ ```python
631
+ class CoursePlannerAgent(BaseAgent):
632
+ __system_message__ = "You design course curricula"
633
+ __description__ = "Creates structured course plans"
634
+
635
+ async def __call__(
636
+ self,
637
+ goal: str,
638
+ experience: str,
639
+ context: Optional[str] = None
640
+ ) -> CoursePlan:
641
+ # Build structured prompt from parameters
642
+ prompt = f"Goal: {goal}\\nExperience: {experience}"
643
+ if context:
644
+ prompt += f"\\nContext: {context}"
645
+ return await self.run(prompt)
646
+
647
+ # Call with structured parameters
648
+ planner = CoursePlannerAgent(model="openai::gpt-4o", api_key=API_KEY)
649
+ course = await planner(
650
+ goal="Learn ML",
651
+ experience="Python beginner",
652
+ context="Prefer hands-on projects"
653
+ )
654
+
655
+ # When linked to another agent, the LLM sees:
656
+ # Tool: planner(goal: str, experience: str, context: Optional[str])
657
+ ```
624
658
  """
625
659
  return await self.run(input_=user_input)
626
660
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyagentic-core
3
- Version: 2.1.0a2
3
+ Version: 2.1.0a3
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.1.0-a.2"
3
+ version = "2.1.0-a.3"
4
4
  description = "Build LLM Agents in a Pythonic way"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"