agnt5 0.2.1__cp39-abi3-manylinux_2_34_x86_64.whl → 0.2.2__cp39-abi3-manylinux_2_34_x86_64.whl

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.

Potentially problematic release.


This version of agnt5 might be problematic. Click here for more details.

agnt5/__init__.py CHANGED
@@ -6,7 +6,7 @@ with built-in durability guarantees and state management.
6
6
  """
7
7
 
8
8
  from ._compat import _import_error, _rust_available
9
- from .agent import Agent, AgentRegistry, AgentResult, agent
9
+ from .agent import Agent, AgentRegistry, AgentResult, Handoff, agent, handoff
10
10
  from .client import Client, RunError
11
11
  from .context import Context
12
12
  from .entity import (
@@ -65,6 +65,8 @@ __all__ = [
65
65
  "Agent",
66
66
  "AgentRegistry",
67
67
  "AgentResult",
68
+ "Handoff",
69
+ "handoff",
68
70
  # Types
69
71
  "RetryPolicy",
70
72
  "BackoffPolicy",
agnt5/_core.abi3.so CHANGED
Binary file
agnt5/_telemetry.py CHANGED
@@ -58,6 +58,11 @@ class OpenTelemetryHandler(logging.Handler):
58
58
  # No Rust bridge available, silently skip
59
59
  return
60
60
 
61
+ # Filter out gRPC internal logs to avoid noise
62
+ # These are low-level HTTP/2 protocol logs that aren't useful for application debugging
63
+ if record.name.startswith(('grpc.', 'h2.', '_grpc_', 'h2-')):
64
+ return
65
+
61
66
  try:
62
67
  # Format the message (applies any formatters)
63
68
  message = self.format(record)
agnt5/agent.py CHANGED
@@ -23,6 +23,97 @@ logger = setup_module_logger(__name__)
23
23
  _AGENT_REGISTRY: Dict[str, "Agent"] = {}
24
24
 
25
25
 
26
+ class Handoff:
27
+ """Configuration for agent-to-agent handoff.
28
+
29
+ Handoffs enable one agent to delegate control to another specialized agent,
30
+ following the pattern popularized by LangGraph and OpenAI Agents SDK.
31
+
32
+ The handoff is exposed to the LLM as a tool named 'transfer_to_{agent_name}'
33
+ that allows explicit delegation with conversation history.
34
+
35
+ Example:
36
+ ```python
37
+ specialist = Agent(name="specialist", ...)
38
+
39
+ # Create handoff configuration
40
+ handoff_to_specialist = Handoff(
41
+ agent=specialist,
42
+ description="Transfer to specialist for detailed analysis"
43
+ )
44
+
45
+ # Use in coordinator agent
46
+ coordinator = Agent(
47
+ name="coordinator",
48
+ handoffs=[handoff_to_specialist]
49
+ )
50
+ ```
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ agent: "Agent",
56
+ description: Optional[str] = None,
57
+ tool_name: Optional[str] = None,
58
+ pass_full_history: bool = True,
59
+ ):
60
+ """Initialize handoff configuration.
61
+
62
+ Args:
63
+ agent: Target agent to hand off to
64
+ description: Description shown to LLM (defaults to agent instructions)
65
+ tool_name: Custom tool name (defaults to 'transfer_to_{agent_name}')
66
+ pass_full_history: Whether to pass full conversation history to target agent
67
+ """
68
+ self.agent = agent
69
+ self.description = description or agent.instructions or f"Transfer to {agent.name}"
70
+ self.tool_name = tool_name or f"transfer_to_{agent.name}"
71
+ self.pass_full_history = pass_full_history
72
+
73
+
74
+ def handoff(
75
+ agent: "Agent",
76
+ description: Optional[str] = None,
77
+ tool_name: Optional[str] = None,
78
+ pass_full_history: bool = True,
79
+ ) -> Handoff:
80
+ """Create a handoff configuration for agent-to-agent delegation.
81
+
82
+ This is a convenience function for creating Handoff instances with a clean API.
83
+
84
+ Args:
85
+ agent: Target agent to hand off to
86
+ description: Description shown to LLM
87
+ tool_name: Custom tool name
88
+ pass_full_history: Whether to pass full conversation history
89
+
90
+ Returns:
91
+ Handoff configuration
92
+
93
+ Example:
94
+ ```python
95
+ from agnt5 import Agent, handoff
96
+
97
+ research_agent = Agent(name="researcher", ...)
98
+ writer_agent = Agent(name="writer", ...)
99
+
100
+ coordinator = Agent(
101
+ name="coordinator",
102
+ handoffs=[
103
+ handoff(research_agent, "Transfer for research tasks"),
104
+ handoff(writer_agent, "Transfer for writing tasks"),
105
+ ]
106
+ )
107
+ ```
108
+ """
109
+ return Handoff(
110
+ agent=agent,
111
+ description=description,
112
+ tool_name=tool_name,
113
+ pass_full_history=pass_full_history,
114
+ )
115
+
116
+
26
117
  class AgentRegistry:
27
118
  """Registry for agents."""
28
119
 
@@ -54,10 +145,19 @@ class AgentRegistry:
54
145
  class AgentResult:
55
146
  """Result from agent execution."""
56
147
 
57
- def __init__(self, output: str, tool_calls: List[Dict[str, Any]], context: Context):
148
+ def __init__(
149
+ self,
150
+ output: str,
151
+ tool_calls: List[Dict[str, Any]],
152
+ context: Context,
153
+ handoff_to: Optional[str] = None,
154
+ handoff_metadata: Optional[Dict[str, Any]] = None,
155
+ ):
58
156
  self.output = output
59
157
  self.tool_calls = tool_calls
60
158
  self.context = context
159
+ self.handoff_to = handoff_to # Name of agent that was handed off to
160
+ self.handoff_metadata = handoff_metadata or {} # Additional handoff info
61
161
 
62
162
 
63
163
  class Agent:
@@ -104,6 +204,7 @@ class Agent:
104
204
  model: str,
105
205
  instructions: str,
106
206
  tools: Optional[List[Any]] = None,
207
+ handoffs: Optional[List[Handoff]] = None,
107
208
  temperature: float = 0.7,
108
209
  max_tokens: Optional[int] = None,
109
210
  top_p: Optional[float] = None,
@@ -116,7 +217,8 @@ class Agent:
116
217
  name: Agent name/identifier
117
218
  model: Model string with provider prefix (e.g., "openai/gpt-4o-mini")
118
219
  instructions: System instructions for the agent
119
- tools: List of tools available to the agent (functions with @tool decorator)
220
+ tools: List of tools available to the agent (functions, Tool instances, or Agent instances)
221
+ handoffs: List of Handoff configurations for agent-to-agent delegation
120
222
  temperature: LLM temperature (0.0 to 1.0)
121
223
  max_tokens: Maximum tokens to generate
122
224
  top_p: Nucleus sampling parameter
@@ -132,12 +234,20 @@ class Agent:
132
234
  self.model_config = model_config
133
235
  self.max_iterations = max_iterations
134
236
 
135
- # Build tool registry
237
+ # Store handoffs for building handoff tools
238
+ self.handoffs = handoffs or []
239
+
240
+ # Build tool registry (includes regular tools, agent-as-tools, and handoff tools)
136
241
  self.tools: Dict[str, Tool] = {}
137
242
  if tools:
138
243
  for tool_item in tools:
244
+ # Check if it's an Agent instance (agents-as-tools pattern)
245
+ if isinstance(tool_item, Agent):
246
+ agent_tool = tool_item.to_tool()
247
+ self.tools[agent_tool.name] = agent_tool
248
+ logger.info(f"Added agent '{tool_item.name}' as tool to '{self.name}'")
139
249
  # Check if it's a Tool instance
140
- if isinstance(tool_item, Tool):
250
+ elif isinstance(tool_item, Tool):
141
251
  self.tools[tool_item.name] = tool_item
142
252
  # Check if it's a decorated function with config
143
253
  elif hasattr(tool_item, "_agnt5_config"):
@@ -154,6 +264,12 @@ class Agent:
154
264
  if tool_instance:
155
265
  self.tools[tool_instance.name] = tool_instance
156
266
 
267
+ # Build handoff tools
268
+ for handoff_config in self.handoffs:
269
+ handoff_tool = self._create_handoff_tool(handoff_config)
270
+ self.tools[handoff_tool.name] = handoff_tool
271
+ logger.info(f"Added handoff tool '{handoff_tool.name}' to '{self.name}'")
272
+
157
273
  self.logger = logging.getLogger(f"agnt5.agent.{name}")
158
274
 
159
275
  # Define schemas based on the run method signature
@@ -180,9 +296,133 @@ class Agent:
180
296
  # Store metadata
181
297
  self.metadata = {
182
298
  "description": instructions,
183
- "model": model_name
299
+ "model": model
184
300
  }
185
301
 
302
+ def to_tool(self, description: Optional[str] = None) -> Tool:
303
+ """Convert this agent to a Tool that can be used by other agents.
304
+
305
+ This enables agents-as-tools pattern where one agent can invoke another
306
+ agent as if it were a regular tool.
307
+
308
+ Args:
309
+ description: Optional custom description (defaults to agent instructions)
310
+
311
+ Returns:
312
+ Tool instance that wraps this agent
313
+
314
+ Example:
315
+ ```python
316
+ research_agent = Agent(
317
+ name="researcher",
318
+ model="openai/gpt-4o-mini",
319
+ instructions="You are a research specialist."
320
+ )
321
+
322
+ # Use research agent as a tool for another agent
323
+ coordinator = Agent(
324
+ name="coordinator",
325
+ model="openai/gpt-4o-mini",
326
+ instructions="Coordinate tasks using specialist agents.",
327
+ tools=[research_agent.to_tool()]
328
+ )
329
+ ```
330
+ """
331
+ agent_name = self.name
332
+
333
+ # Handler that runs the agent
334
+ async def agent_tool_handler(ctx: Context, user_message: str) -> str:
335
+ """Execute agent and return output."""
336
+ ctx.logger.info(f"Invoking agent '{agent_name}' as tool")
337
+
338
+ # Run the agent with the user message
339
+ result = await self.run(user_message, context=ctx)
340
+
341
+ return result.output
342
+
343
+ # Create tool with agent's schema
344
+ tool_description = description or self.instructions or f"Agent: {self.name}"
345
+
346
+ agent_tool = Tool(
347
+ name=self.name,
348
+ description=tool_description,
349
+ handler=agent_tool_handler,
350
+ input_schema=self.input_schema,
351
+ auto_schema=False,
352
+ )
353
+
354
+ return agent_tool
355
+
356
+ def _create_handoff_tool(self, handoff_config: Handoff, current_messages_callback: Optional[Callable] = None) -> Tool:
357
+ """Create a tool for handoff to another agent.
358
+
359
+ Args:
360
+ handoff_config: Handoff configuration
361
+ current_messages_callback: Optional callback to get current conversation messages
362
+
363
+ Returns:
364
+ Tool instance that executes the handoff
365
+ """
366
+ target_agent = handoff_config.agent
367
+ tool_name = handoff_config.tool_name
368
+
369
+ # Handler that executes the handoff
370
+ async def handoff_handler(ctx: Context, message: str) -> Dict[str, Any]:
371
+ """Transfer control to target agent."""
372
+ ctx.logger.info(
373
+ f"Handoff from '{self.name}' to '{target_agent.name}': {message}"
374
+ )
375
+
376
+ # If we should pass conversation history, add it to context
377
+ if handoff_config.pass_full_history:
378
+ # Get current conversation from the agent's run loop
379
+ # (This will be set when we detect the handoff in run())
380
+ conversation_history = ctx.get("_current_conversation", [])
381
+ if conversation_history:
382
+ ctx.logger.info(
383
+ f"Passing {len(conversation_history)} messages to target agent"
384
+ )
385
+ # Store in context for target agent to optionally use
386
+ ctx.set("_handoff_conversation_history", conversation_history)
387
+
388
+ # Execute target agent with the message and shared context
389
+ result = await target_agent.run(message, context=ctx)
390
+
391
+ # Store handoff metadata - this signals that a handoff occurred
392
+ handoff_data = {
393
+ "_handoff": True,
394
+ "from_agent": self.name,
395
+ "to_agent": target_agent.name,
396
+ "message": message,
397
+ "output": result.output,
398
+ "tool_calls": result.tool_calls,
399
+ }
400
+
401
+ ctx.set("_handoff_result", handoff_data)
402
+
403
+ # Return the handoff data (will be detected in run() loop)
404
+ return handoff_data
405
+
406
+ # Create tool with handoff schema
407
+ handoff_tool = Tool(
408
+ name=tool_name,
409
+ description=handoff_config.description,
410
+ handler=handoff_handler,
411
+ input_schema={
412
+ "type": "object",
413
+ "properties": {
414
+ "message": {
415
+ "type": "string",
416
+ "description": "Message or task to pass to the target agent"
417
+ }
418
+ },
419
+ "required": ["message"]
420
+ },
421
+ auto_schema=False,
422
+ )
423
+
424
+ return handoff_tool
425
+
186
426
  async def run(
187
427
  self,
188
428
  user_message: str,
@@ -266,6 +506,9 @@ class Agent:
266
506
  if response.tool_calls:
267
507
  self.logger.info(f"Agent calling {len(response.tool_calls)} tool(s)")
268
508
 
509
+ # Store current conversation in context for potential handoffs
510
+ context.set("_current_conversation", messages)
511
+
269
512
  # Execute tool calls
270
513
  tool_results = []
271
514
  for tool_call in response.tool_calls:
@@ -293,6 +536,22 @@ class Agent:
293
536
  else:
294
537
  # Execute tool
295
538
  result = await tool.invoke(context, **tool_args)
539
+
540
+ # Check if this was a handoff
541
+ if isinstance(result, dict) and result.get("_handoff"):
542
+ self.logger.info(
543
+ f"Handoff detected to '{result['to_agent']}', "
544
+ f"terminating current agent"
545
+ )
546
+ # Return immediately with handoff result
547
+ return AgentResult(
548
+ output=result["output"],
549
+ tool_calls=all_tool_calls + result.get("tool_calls", []),
550
+ context=context,
551
+ handoff_to=result["to_agent"],
552
+ handoff_metadata=result,
553
+ )
554
+
296
555
  result_text = json.dumps(result) if result else "null"
297
556
 
298
557
  tool_results.append(
agnt5/entity.py CHANGED
@@ -646,6 +646,10 @@ class DurableEntity:
646
646
  if cls.__name__ == 'DurableEntity':
647
647
  return
648
648
 
649
+ # Don't register SDK's built-in base classes (these are meant to be extended by users)
650
+ if cls.__name__ in ('SessionEntity', 'MemoryEntity', 'WorkflowEntity'):
651
+ return
652
+
649
653
  # Create an EntityType for this class, storing the class reference
650
654
  entity_type = EntityType(cls.__name__, entity_class=cls)
651
655
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agnt5
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Programming Language :: Python :: 3
@@ -1,13 +1,13 @@
1
- agnt5-0.2.1.dist-info/METADATA,sha256=v9zvBDwH_NDiaC32nHGAEWSlNapxQNHJo8dY-XfrPpY,965
2
- agnt5-0.2.1.dist-info/WHEEL,sha256=_MdxKRqVzoLVgxWY0NC1kE3N9tUha8jIssn_BZnT0VE,106
3
- agnt5/__init__.py,sha256=pvhnwgCYB_3fVnfECqRcbVHD36tCUT8ErON-HxIF_HA,1885
1
+ agnt5-0.2.2.dist-info/METADATA,sha256=lGJESH9_HBx_FmDngvJl_cDBCF02SfJsL_mJlWusN1E,965
2
+ agnt5-0.2.2.dist-info/WHEEL,sha256=_MdxKRqVzoLVgxWY0NC1kE3N9tUha8jIssn_BZnT0VE,106
3
+ agnt5/__init__.py,sha256=dQ83SuDrAU83leQ38mDVdthZFP0SWJMK67xq1d4uGtg,1933
4
4
  agnt5/_compat.py,sha256=BGuy3v5VDOHVa5f3Z-C22iMN19lAt0mPmXwF3qSSWxI,369
5
- agnt5/_core.abi3.so,sha256=woGUa9BYg-xM-zMg7GnRU-_C7W4oa1LdsnpF64A0d0w,14946400
6
- agnt5/_telemetry.py,sha256=6pEGUrMpwvcCBAZ-I1HnkGsxedfyN_x9ZcW2U0lZonM,4658
7
- agnt5/agent.py,sha256=qITzGiLUkPLe1QXoplu8DbZ0O4yPfQIuoAzEwHWQ_yQ,16984
5
+ agnt5/_core.abi3.so,sha256=YAs-5MuquYB48KNrqDB2HTIlNQIQwUP5x80ieoBgPxo,15042320
6
+ agnt5/_telemetry.py,sha256=ZeABlTMoEnXYnBFVOn6ruaSvfeS7r_czI_Pk_tiCfhY,4899
7
+ agnt5/agent.py,sha256=sU0qSZyXl_D6YrQKxtlyR6gggPJ0zZKC63bWtJvMzJI,26557
8
8
  agnt5/client.py,sha256=Zesl-TIie8Gwq-ajTauPlAsF7jZm5TPk307iHdxQ2SM,21214
9
9
  agnt5/context.py,sha256=fF_eL_rY-GFnGFpL6wqPERykluGf2obpD-mYrGpsUTc,23817
10
- agnt5/entity.py,sha256=4wAcjRT03RanUVSDsDEkEb6_R_0KE0MijcAZ8krO8zI,43771
10
+ agnt5/entity.py,sha256=kLMBlHSQJeRqNwfWcXeLcM5UF9JcU8aET6TQBh6tSBA,43966
11
11
  agnt5/exceptions.py,sha256=mZ0q-NK6OKhYxgwBJpIbgpgzk-CJaFIHDbp1EE-pS7I,925
12
12
  agnt5/function.py,sha256=hH7rH9c_ZohUuNoLlBJHuiR5qPhw0L0pkrVVrpL_g2M,11273
13
13
  agnt5/lm.py,sha256=VvKVm_7VneBggLoCZEXfTavpPJJXNpbARrgHPCqhrX8,18915
@@ -16,4 +16,4 @@ agnt5/types.py,sha256=Zb71ZMwvrt1p4SH18cAKunp2y5tao_W5_jGYaPDejQo,2840
16
16
  agnt5/version.py,sha256=rOq1mObLihnnKgKqBrwZA0zwOPudEKVFcW1a48ynkqc,573
17
17
  agnt5/worker.py,sha256=n4zL4DUocRlcpitsOPbrZ4frz-E_ZoVgIhKH_76iq00,31502
18
18
  agnt5/workflow.py,sha256=1sFAvtdZn6n7nDDb0YyE6LGJGuN2KVFNVo9Q42N_7_k,4529
19
- agnt5-0.2.1.dist-info/RECORD,,
19
+ agnt5-0.2.2.dist-info/RECORD,,
File without changes