pyagentic-core 2.1.0__tar.gz → 2.1.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 (45) hide show
  1. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/PKG-INFO +1 -1
  2. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/_agent/_agent.py +58 -92
  3. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic_core.egg-info/PKG-INFO +1 -1
  4. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyproject.toml +1 -1
  5. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/LICENSE +0 -0
  6. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/README.md +0 -0
  7. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/__init__.py +0 -0
  8. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/__init__.py +0 -0
  9. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/_agent/__init__.py +0 -0
  10. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/_agent/_agent_linking.py +0 -0
  11. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/_agent/_agent_state.py +0 -0
  12. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/_exceptions.py +0 -0
  13. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/_info.py +0 -0
  14. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/_metaclasses.py +0 -0
  15. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/_ref.py +0 -0
  16. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/_spec.py +0 -0
  17. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/_state.py +0 -0
  18. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/_tool.py +0 -0
  19. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_base/_validation.py +0 -0
  20. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_utils/_typing.py +0 -0
  21. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/_utils/_warnings.py +0 -0
  22. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/llm/__init__.py +0 -0
  23. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/llm/_anthropic.py +0 -0
  24. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/llm/_gemini.py +0 -0
  25. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/llm/_mock.py +0 -0
  26. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/llm/_openai.py +0 -0
  27. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/llm/_openaiv1.py +0 -0
  28. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/llm/_provider.py +0 -0
  29. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/logging.py +0 -0
  30. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/models/llm.py +0 -0
  31. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/models/response.py +0 -0
  32. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/models/tracing.py +0 -0
  33. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/policies/__init__.py +0 -0
  34. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/policies/_events.py +0 -0
  35. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/policies/_policy.py +0 -0
  36. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/tracing/__init__.py +0 -0
  37. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/tracing/_basic.py +0 -0
  38. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/tracing/_langfuse.py +0 -0
  39. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/tracing/_tracer.py +0 -0
  40. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic/updates.py +0 -0
  41. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic_core.egg-info/SOURCES.txt +0 -0
  42. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic_core.egg-info/dependency_links.txt +0 -0
  43. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic_core.egg-info/requires.txt +0 -0
  44. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/pyagentic_core.egg-info/top_level.txt +0 -0
  45. {pyagentic_core-2.1.0 → pyagentic_core-2.1.0a2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyagentic-core
3
- Version: 2.1.0
3
+ Version: 2.1.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
@@ -126,6 +126,8 @@ 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)
129
131
  tracer (AgentTracer, optional): Tracer instance for observability. Defaults
130
132
  to BasicTracer if not provided.
131
133
  max_call_depth (int): Maximum number of tool calling loops per run. Defaults to 1.
@@ -175,6 +177,7 @@ class BaseAgent(metaclass=AgentMeta):
175
177
  model: str = None
176
178
  api_key: str = None
177
179
  provider: LLMProvider = None
180
+ emitter: Callable[[Any], str] = None
178
181
  tracer: AgentTracer = None
179
182
  max_call_depth: int = 1
180
183
 
@@ -311,6 +314,8 @@ class BaseAgent(metaclass=AgentMeta):
311
314
  except Exception as e:
312
315
  # Handle inference errors gracefully
313
316
  logger.exception(e)
317
+ if self.emitter:
318
+ await _safe_run(self.emitter, EmitUpdate(status=Status.ERROR))
314
319
  # Add error message to conversation history
315
320
  self.state._messages.append(
316
321
  Message(role="assistant", content="Failed to generate a response")
@@ -397,15 +402,37 @@ class BaseAgent(metaclass=AgentMeta):
397
402
  # Handle validation errors for tool arguments
398
403
  result = f"Function Args were invalid: {str(e)}"
399
404
  compiled_args = {}
400
- self.tracer.record_exception(str(e))
401
- logger.exception(e)
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
402
417
  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
+ )
403
425
  if compiled_args:
404
426
  result = await _safe_run(handler, **compiled_args)
405
427
  self.tracer.set_attributes(result=result)
406
428
  except TypeError as e:
407
429
  self.tracer.record_exception(str(e))
408
430
  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
+ )
409
436
  raise InvalidToolDefinition(
410
437
  tool_name=tool_call.name,
411
438
  message=f"Tool must have a serializable return type; {tool_def.return_type} failed to be casted to a string.",
@@ -415,6 +442,11 @@ class BaseAgent(metaclass=AgentMeta):
415
442
  self.tracer.record_exception(str(e))
416
443
  logger.exception(e)
417
444
  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
+ )
418
450
 
419
451
  stringified_result = (
420
452
  result.model_dump_json(indent=2)
@@ -460,12 +492,10 @@ class BaseAgent(metaclass=AgentMeta):
460
492
  self, input_: str
461
493
  ) -> AsyncGenerator[Union[ToolResponse, AgentResponse, LLMResponse]]:
462
494
  """
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.
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.
466
497
 
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:
498
+ The agent follows an agentic loop pattern:
469
499
  1. Send user input and conversation history to the LLM
470
500
  2. LLM decides to either call tools or respond with final output
471
501
  3. If tools are called, execute them and feed results back to LLM
@@ -474,23 +504,19 @@ class BaseAgent(metaclass=AgentMeta):
474
504
  Args:
475
505
  input_ (str): The user input/query for the agent to process
476
506
 
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
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
482
513
 
483
514
  Example:
484
515
  ```python
485
516
  agent = MyAgent(model="openai::gpt-4o", api_key=API_KEY)
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}")
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
494
520
  ```
495
521
  """
496
522
  async with self.tracer.agent(
@@ -512,6 +538,10 @@ class BaseAgent(metaclass=AgentMeta):
512
538
  agent_responses: list = []
513
539
  processed_call_ids: set[str] = set()
514
540
 
541
+ # Emit initial status
542
+ if self.emitter:
543
+ await _safe_run(self.emitter, EmitUpdate(status=Status.GENERATING))
544
+
515
545
  # Main agentic loop: LLM -> Tools -> LLM -> ...
516
546
  depth = 0
517
547
  final_ai_output: str | None = None
@@ -554,6 +584,12 @@ class BaseAgent(metaclass=AgentMeta):
554
584
  response = await self._process_llm_inference()
555
585
  final_ai_output = response.parsed if response.parsed else response.text
556
586
 
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
+
557
593
  # Build the structured response
558
594
  response_fields = {
559
595
  "final_output": final_ai_output,
@@ -571,32 +607,6 @@ class BaseAgent(metaclass=AgentMeta):
571
607
  yield response
572
608
 
573
609
  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
- """
600
610
  final_response = None
601
611
  async for res in self.step(input_):
602
612
  final_response = res
@@ -604,57 +614,13 @@ class BaseAgent(metaclass=AgentMeta):
604
614
 
605
615
  async def __call__(self, user_input: str) -> BaseModel:
606
616
  """
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:
617
+ Allows the agent to be called directly as a function.
616
618
 
617
619
  Args:
618
- user_input (str): The user input to process (default implementation)
620
+ user_input (str): The user input to process
619
621
 
620
622
  Returns:
621
623
  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
- ```
658
624
  """
659
625
  return await self.run(input_=user_input)
660
626
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyagentic-core
3
- Version: 2.1.0
3
+ Version: 2.1.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyagentic-core"
3
- version = "2.1.0"
3
+ version = "2.1.0-a.2"
4
4
  description = "Build LLM Agents in a Pythonic way"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
File without changes