aury-agent 0.0.5__py3-none-any.whl → 0.0.7__py3-none-any.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.
aury/agents/core/base.py CHANGED
@@ -49,8 +49,17 @@ class AgentConfig:
49
49
  Note: LLM parameters (temperature, max_tokens, timeout, retries) are
50
50
  configured on LLMProvider, not here. Tool timeout is configured on
51
51
  each tool's ToolConfig, not here.
52
+
53
+ Agent identity fields (for ActorInfo):
54
+ - id: Database ID or unique identifier (e.g. "1", "agent_123")
55
+ - code: Agent code/type (e.g. "super_assistant", "researcher")
56
+ - name: Display name (e.g. "超级助理", "Researcher Agent")
52
57
  """
53
- name: str = "default"
58
+ # Agent identity
59
+ id: str | None = None # Database ID
60
+ code: str | None = None # Agent code (used as fallback for id)
61
+ name: str | None = None # Display name
62
+
54
63
  max_steps: int = 50
55
64
 
56
65
  # System prompt configuration
@@ -147,19 +147,31 @@ async def emit(event: "BlockEvent | ActionEvent") -> None:
147
147
  Use this when you don't have access to InvocationContext,
148
148
  e.g., in tool execute() methods.
149
149
 
150
- For BlockEvent: automatically fills parent_id from ContextVar if not set.
151
- ActionEvent does not have parent_id.
150
+ For BlockEvent: automatically fills parent_id and actor from ContextVar if not set.
151
+ ActionEvent does not have parent_id or actor.
152
152
 
153
153
  Args:
154
154
  event: BlockEvent or ActionEvent to emit
155
155
  """
156
156
  try:
157
+ # Get current context for auto-fill
158
+ ctx = get_current_ctx_or_none()
159
+
157
160
  # Auto-fill parent_id from ContextVar if not explicitly set (BlockEvent only)
158
161
  if hasattr(event, 'parent_id') and event.parent_id is None:
159
162
  from .types.block import BlockKind
160
163
  kind = event.kind.value if isinstance(event.kind, BlockKind) else event.kind
161
164
  event.parent_id = resolve_parent_id(kind)
162
165
 
166
+ # Auto-fill actor from context if not explicitly set (BlockEvent only)
167
+ if hasattr(event, 'actor') and event.actor is None and ctx:
168
+ from .types.block import ActorInfo
169
+ event.actor = ActorInfo(
170
+ id=ctx.agent_id,
171
+ role="assistant",
172
+ name=ctx.agent_name,
173
+ )
174
+
163
175
  queue = _emit_queue_var.get()
164
176
  await queue.put(event)
165
177
  # Yield control to event loop to allow consumer to process the queue
@@ -224,6 +236,7 @@ class InvocationContext:
224
236
  session: "Session"
225
237
  invocation_id: str
226
238
  agent_id: str
239
+ agent_name: str | None = None # Agent display name (for ActorInfo)
227
240
 
228
241
  # Backends container (unified backend access)
229
242
  backends: "Backends | None" = None
@@ -272,6 +285,10 @@ class InvocationContext:
272
285
  config: dict[str, Any] = field(default_factory=dict)
273
286
  metadata: dict[str, Any] = field(default_factory=dict)
274
287
 
288
+ # Block schema versions mapping: kind -> schema_version
289
+ # External can register different schema versions for the same kind
290
+ block_schema_versions: dict[str, str] = field(default_factory=dict)
291
+
275
292
  # Depth tracking (for max depth enforcement)
276
293
  _depth: int = 0
277
294
 
@@ -404,6 +421,7 @@ class InvocationContext:
404
421
  def create_child(
405
422
  self,
406
423
  agent_id: str,
424
+ agent_name: str | None = None,
407
425
  mode: str = "delegated",
408
426
  inherit_config: bool = True,
409
427
  llm: "LLMProvider | None" = None,
@@ -418,6 +436,7 @@ class InvocationContext:
418
436
 
419
437
  Args:
420
438
  agent_id: Sub-agent ID
439
+ agent_name: Sub-agent display name (optional)
421
440
  mode: Execution mode (delegated)
422
441
  inherit_config: Whether to copy config
423
442
  llm: Override LLM provider (None = inherit from parent)
@@ -461,6 +480,7 @@ class InvocationContext:
461
480
  session=self.session,
462
481
  invocation_id=generate_id("inv"),
463
482
  agent_id=agent_id,
483
+ agent_name=agent_name,
464
484
  backends=self.backends, # Inherit backends
465
485
  bus=self.bus,
466
486
  llm=llm if llm is not None else self.llm,
@@ -476,6 +496,7 @@ class InvocationContext:
476
496
  abort_chain=self.abort_chain, # Shared abort_chain
477
497
  config=self.config.copy() if inherit_config else {},
478
498
  metadata={"parent_block_id": parent_block_id} if parent_block_id else {},
499
+ block_schema_versions=self.block_schema_versions.copy() if inherit_config else {},
479
500
  _depth=self._depth + 1,
480
501
  )
481
502
 
@@ -485,6 +506,7 @@ class InvocationContext:
485
506
  session=self.session,
486
507
  invocation_id=self.invocation_id,
487
508
  agent_id=self.agent_id,
509
+ agent_name=self.agent_name,
488
510
  backends=self.backends, # Inherit backends
489
511
  bus=self.bus,
490
512
  llm=self.llm,
@@ -499,6 +521,7 @@ class InvocationContext:
499
521
  abort_chain=self.abort_chain,
500
522
  config=self.config,
501
523
  metadata=self.metadata.copy(),
524
+ block_schema_versions=self.block_schema_versions,
502
525
  _depth=self._depth,
503
526
  )
504
527
 
@@ -574,13 +597,17 @@ class InvocationContext:
574
597
  - Tool outputs
575
598
  - BlockHandle operations
576
599
 
577
- The block's session_id, invocation_id, and parent_id are automatically filled.
578
- Uses ContextVar to find the queue set by BaseAgent.run().
579
- Parent_id respects the apply_to_kinds filter set via set_parent_id().
600
+ The block's session_id, invocation_id, parent_id, actor, and schema_version
601
+ are automatically filled. Uses ContextVar to find the queue set by
602
+ BaseAgent.run(). Parent_id respects the apply_to_kinds filter set
603
+ via set_parent_id().
580
604
 
581
605
  Args:
582
606
  block: BlockEvent to emit
583
607
  """
608
+ from .types.block import BlockKind, ActorInfo
609
+ kind = block.kind.value if isinstance(block.kind, BlockKind) else block.kind
610
+
584
611
  # Fill in IDs if not set
585
612
  if not block.session_id:
586
613
  block.session_id = self.session_id
@@ -589,10 +616,21 @@ class InvocationContext:
589
616
  # Auto-fill parent_id from ContextVar if not explicitly set
590
617
  # Uses resolve_parent_id to respect apply_to_kinds filter
591
618
  if block.parent_id is None:
592
- from .types.block import BlockKind
593
- kind = block.kind.value if isinstance(block.kind, BlockKind) else block.kind
594
619
  block.parent_id = resolve_parent_id(kind)
595
620
 
621
+ # Auto-fill actor if not explicitly set
622
+ # Actor represents the agent that emitted this block
623
+ if block.actor is None:
624
+ block.actor = ActorInfo(
625
+ id=self.agent_id,
626
+ role="assistant",
627
+ name=self.agent_name,
628
+ )
629
+
630
+ # Auto-fill schema_version from config if not explicitly set
631
+ if block.schema_version is None and kind in self.block_schema_versions:
632
+ block.schema_version = self.block_schema_versions[kind]
633
+
596
634
  # Put into the current run's queue (via ContextVar)
597
635
  try:
598
636
  queue = _emit_queue_var.get()
@@ -639,17 +677,9 @@ class InvocationContext:
639
677
  **kwargs,
640
678
  }
641
679
 
642
- # Build context for middleware
643
- mw_context = {
644
- "session_id": self.session_id,
645
- "invocation_id": self.invocation_id,
646
- "agent_id": self.agent_id,
647
- "step": self.step,
648
- }
649
-
650
680
  # Process through middleware (on_request)
651
681
  if self.middleware:
652
- processed = await self.middleware.process_request(request, mw_context)
682
+ processed = await self.middleware.process_request(request)
653
683
  if processed is None:
654
684
  logger.debug("Request blocked by middleware")
655
685
  return None
@@ -658,7 +688,7 @@ class InvocationContext:
658
688
  try:
659
689
  # Call LLM
660
690
  if stream:
661
- return self._stream_llm_with_middleware(provider, request, mw_context)
691
+ return self._stream_llm_with_middleware(provider, request)
662
692
  else:
663
693
  response = await provider.generate(
664
694
  messages=request["messages"],
@@ -668,7 +698,7 @@ class InvocationContext:
668
698
  # Process through middleware (on_response)
669
699
  if self.middleware:
670
700
  response_dict = {"response": response}
671
- processed = await self.middleware.process_response(response_dict, mw_context)
701
+ processed = await self.middleware.process_response(response_dict)
672
702
  if processed is None:
673
703
  return None
674
704
  response = processed.get("response", response)
@@ -677,7 +707,7 @@ class InvocationContext:
677
707
 
678
708
  except Exception as e:
679
709
  if self.middleware:
680
- processed_error = await self.middleware.process_error(e, mw_context)
710
+ processed_error = await self.middleware.process_error(e)
681
711
  if processed_error is None:
682
712
  return None
683
713
  raise processed_error
@@ -687,7 +717,6 @@ class InvocationContext:
687
717
  self,
688
718
  provider: "LLMProvider",
689
719
  request: dict[str, Any],
690
- mw_context: dict[str, Any],
691
720
  ) -> AsyncIterator[Any]:
692
721
  """Stream LLM response with middleware processing."""
693
722
  if self.middleware:
@@ -700,7 +729,7 @@ class InvocationContext:
700
729
  ):
701
730
  if self.middleware:
702
731
  chunk_dict = {"chunk": chunk}
703
- processed = await self.middleware.process_stream_chunk(chunk_dict, mw_context)
732
+ processed = await self.middleware.process_stream_chunk(chunk_dict)
704
733
  if processed is None:
705
734
  continue
706
735
  chunk = processed.get("chunk", chunk)
@@ -708,7 +737,7 @@ class InvocationContext:
708
737
 
709
738
  except Exception as e:
710
739
  if self.middleware:
711
- processed_error = await self.middleware.process_error(e, mw_context)
740
+ processed_error = await self.middleware.process_error(e)
712
741
  if processed_error is None:
713
742
  return
714
743
  raise processed_error
@@ -733,19 +762,11 @@ class InvocationContext:
733
762
  """
734
763
  from ..middleware import HookAction
735
764
 
736
- # Build context for middleware
737
- mw_context = {
738
- "session_id": self.session_id,
739
- "invocation_id": self.invocation_id,
740
- "agent_id": self.agent_id,
741
- "step": self.step,
742
- }
743
-
744
765
  current_args = arguments
745
766
 
746
767
  # Process through middleware (on_tool_call)
747
768
  if self.middleware:
748
- result = await self.middleware.process_tool_call(tool, current_args, mw_context)
769
+ result = await self.middleware.process_tool_call(tool, current_args)
749
770
  if result.action == HookAction.SKIP:
750
771
  logger.debug(f"Tool {tool.name} skipped by middleware")
751
772
  from ..tool import ToolResult
@@ -768,7 +789,7 @@ class InvocationContext:
768
789
 
769
790
  # Process through middleware (on_tool_end)
770
791
  if self.middleware:
771
- result = await self.middleware.process_tool_end(tool, tool_result, mw_context)
792
+ result = await self.middleware.process_tool_end(tool, tool_result)
772
793
  if result.action == HookAction.RETRY and result.modified_data:
773
794
  # Re-execute with modified args
774
795
  tool_result = await tool.execute(**result.modified_data)
@@ -94,8 +94,6 @@ class EventBus:
94
94
  # Wildcard match
95
95
  handlers.extend(self._subscriptions.get("*", []))
96
96
 
97
- logger.debug("Publishing event", extra={"event_type": event_type})
98
-
99
97
  for handler in handlers:
100
98
  try:
101
99
  if asyncio.iscoroutinefunction(handler):
@@ -129,7 +129,12 @@ class AgentFactory:
129
129
  Agent instance with child context
130
130
  """
131
131
  logger.debug(f"AgentFactory.create_subagent: creating subagent, type={agent_type}, mode={mode}, parent_invocation_id={parent_ctx.invocation_id}")
132
- child_ctx = parent_ctx.create_child(agent_id=agent_type, mode=mode)
132
+
133
+ # Get agent name from class if available
134
+ agent_class = self._registry.get(agent_type)
135
+ agent_name = getattr(agent_class, 'name', agent_type) if agent_class else agent_type
136
+
137
+ child_ctx = parent_ctx.create_child(agent_id=agent_type, agent_name=agent_name, mode=mode)
133
138
  logger.debug(f"AgentFactory.create_subagent: child context created, child_invocation_id={child_ctx.invocation_id}")
134
139
  return self.create(agent_type, child_ctx, config)
135
140
 
@@ -21,7 +21,29 @@ Pre-defined loggers:
21
21
  - session: aury.agents.session
22
22
  """
23
23
  import logging
24
- from typing import Literal
24
+ from typing import Any, Literal
25
+
26
+ # =============================================================================
27
+ # TRACE Level 支持
28
+ # =============================================================================
29
+ # TRACE (5) < DEBUG (10),用于超细粒度调试
30
+ TRACE = 5
31
+ if logging.getLevelName(TRACE) == "Level 5":
32
+ logging.addLevelName(TRACE, "TRACE")
33
+
34
+
35
+ def _ensure_trace_method() -> None:
36
+ """确保 logging.Logger 有 trace() 方法。"""
37
+ if hasattr(logging.Logger, "trace"):
38
+ return
39
+
40
+ def trace(self: logging.Logger, msg: str, *args: Any, **kwargs: Any) -> None:
41
+ if self.isEnabledFor(TRACE):
42
+ self._log(TRACE, msg, args, **kwargs)
43
+
44
+ logging.Logger.trace = trace # type: ignore[attr-defined]
45
+
46
+ _ensure_trace_method()
25
47
 
26
48
  # Root namespace
27
49
  ROOT_NAMESPACE = "aury.agents"
@@ -79,6 +101,7 @@ LoggerName = Literal[
79
101
 
80
102
 
81
103
  __all__ = [
104
+ "TRACE",
82
105
  "get_logger",
83
106
  "logger",
84
107
  "react_logger",
@@ -110,6 +110,7 @@ class BlockEvent:
110
110
  op: BlockOp = BlockOp.APPLY
111
111
  data: dict[str, Any] | None = None
112
112
  branch: str | None = None # For sub-agent isolation (e.g. "agent1.agent2")
113
+ schema_version: str | None = None # Schema version for external rendering/handling
113
114
 
114
115
  # Protocol envelope
115
116
  protocol_version: str = "1.0"
@@ -130,6 +131,7 @@ class BlockEvent:
130
131
  "op": self.op.value,
131
132
  "data": self.data,
132
133
  "branch": self.branch,
134
+ "schema_version": self.schema_version,
133
135
  "protocol_version": self.protocol_version,
134
136
  "event_id": self.event_id,
135
137
  "timestamp": self.timestamp,
@@ -154,6 +156,7 @@ class BlockEvent:
154
156
  op=BlockOp(data["op"]),
155
157
  data=data.get("data"),
156
158
  branch=data.get("branch"),
159
+ schema_version=data.get("schema_version"),
157
160
  protocol_version=data.get("protocol_version", "1.0"),
158
161
  event_id=data.get("event_id", ""),
159
162
  timestamp=data.get("timestamp", 0),
@@ -310,8 +313,7 @@ class PersistedBlock:
310
313
  # From protocol envelope
311
314
  session_id: str = ""
312
315
  invocation_id: str = ""
313
- actor_id: str = ""
314
- actor_role: str = "assistant"
316
+ actor: ActorInfo | None = None
315
317
 
316
318
  # Branch for sub-agent isolation
317
319
  branch: str | None = None
@@ -332,8 +334,7 @@ class PersistedBlock:
332
334
  "data": self.data,
333
335
  "session_id": self.session_id,
334
336
  "invocation_id": self.invocation_id,
335
- "actor_id": self.actor_id,
336
- "actor_role": self.actor_role,
337
+ "actor": self.actor.to_dict() if self.actor else None,
337
338
  "branch": self.branch,
338
339
  "created_at": self.created_at.isoformat(),
339
340
  "updated_at": self.updated_at.isoformat() if self.updated_at else None,
@@ -355,8 +356,7 @@ class PersistedBlock:
355
356
  data=data.get("data"),
356
357
  session_id=data.get("session_id", ""),
357
358
  invocation_id=data.get("invocation_id", ""),
358
- actor_id=data.get("actor_id", ""),
359
- actor_role=data.get("actor_role", "assistant"),
359
+ actor=ActorInfo.from_dict(data["actor"]) if data.get("actor") else None,
360
360
  branch=data.get("branch"),
361
361
  created_at=datetime.fromisoformat(data["created_at"]),
362
362
  updated_at=datetime.fromisoformat(data["updated_at"]) if data.get("updated_at") else None,
@@ -395,8 +395,7 @@ class PersistedBlock:
395
395
  branch=first.branch,
396
396
  session_id=first.session_id,
397
397
  invocation_id=first.invocation_id,
398
- actor_id=first.actor.id if first.actor else "",
399
- actor_role=first.actor.role if first.actor else "assistant",
398
+ actor=first.actor,
400
399
  updated_at=datetime.fromtimestamp(last_timestamp / 1000) if len(events) > 1 else None,
401
400
  )
402
401
 
@@ -471,8 +470,7 @@ class BlockAggregator:
471
470
  branch=event.branch,
472
471
  session_id=event.session_id,
473
472
  invocation_id=event.invocation_id,
474
- actor_id=event.actor.id if event.actor else "",
475
- actor_role=event.actor.role if event.actor else "assistant",
473
+ actor=event.actor,
476
474
  )
477
475
  self._blocks[block_id] = block
478
476
  self._order.append(block_id)
@@ -533,6 +531,7 @@ class BlockHandle:
533
531
  parent: "BlockHandle | str | None" = None,
534
532
  persistence: Persistence = Persistence.PERSISTENT,
535
533
  branch: str | None = None,
534
+ schema_version: str | None = None,
536
535
  ):
537
536
  self.ctx = ctx
538
537
  self.kind = kind
@@ -541,6 +540,8 @@ class BlockHandle:
541
540
  self.persistence = persistence
542
541
  # Use provided branch or get from context
543
542
  self.branch = branch or getattr(ctx, 'branch', None)
543
+ # Schema version (None = auto-fill from ctx.block_schema_versions)
544
+ self.schema_version = schema_version
544
545
 
545
546
  async def apply(self, data: dict[str, Any], **kwargs: Any) -> None:
546
547
  """Create or completely replace the Block."""
@@ -552,6 +553,7 @@ class BlockHandle:
552
553
  op=BlockOp.APPLY,
553
554
  data=data,
554
555
  branch=self.branch,
556
+ schema_version=self.schema_version,
555
557
  **kwargs,
556
558
  ))
557
559
 
@@ -565,6 +567,7 @@ class BlockHandle:
565
567
  op=BlockOp.DELTA,
566
568
  data=data,
567
569
  branch=self.branch,
570
+ schema_version=self.schema_version,
568
571
  ))
569
572
 
570
573
  async def patch(self, data: dict[str, Any]) -> None:
@@ -577,6 +580,7 @@ class BlockHandle:
577
580
  op=BlockOp.PATCH,
578
581
  data=data,
579
582
  branch=self.branch,
583
+ schema_version=self.schema_version,
580
584
  ))
581
585
 
582
586
  def child(
@@ -584,6 +588,7 @@ class BlockHandle:
584
588
  kind: BlockKind | str,
585
589
  block_id: str | None = None,
586
590
  persistence: Persistence = Persistence.PERSISTENT,
591
+ schema_version: str | None = None,
587
592
  ) -> "BlockHandle":
588
593
  """Create a child BlockHandle nested under this Block."""
589
594
  return BlockHandle(
@@ -593,6 +598,7 @@ class BlockHandle:
593
598
  parent=self,
594
599
  persistence=persistence,
595
600
  branch=self.branch, # Inherit branch from parent
601
+ schema_version=schema_version, # Child can have different schema_version
596
602
  )
597
603
 
598
604
 
@@ -58,6 +58,7 @@ class ModelClientProvider:
58
58
  api_key: str | None = None,
59
59
  base_url: str | None = None,
60
60
  capabilities: Capabilities | None = None,
61
+ timeout: float | None = None,
61
62
  **kwargs: Any,
62
63
  ):
63
64
  """Initialize ModelClient provider.
@@ -68,6 +69,7 @@ class ModelClientProvider:
68
69
  api_key: API key (optional, uses env if not provided)
69
70
  base_url: Base URL override
70
71
  capabilities: Model capabilities
72
+ timeout: Request timeout in seconds (None = use provider default, typically 600s)
71
73
  **kwargs: Additional ModelClient options
72
74
  """
73
75
  if not HAS_MODEL_CLIENT:
@@ -90,6 +92,8 @@ class ModelClientProvider:
90
92
  client_kwargs["api_key"] = api_key
91
93
  if base_url:
92
94
  client_kwargs["base_url"] = base_url
95
+ if timeout is not None:
96
+ client_kwargs["timeout"] = timeout
93
97
 
94
98
  # Pass through additional options
95
99
  for key in ("default_max_tokens", "default_temperature", "default_top_p"):
@@ -381,6 +385,7 @@ class ModelClientProvider:
381
385
  def create_provider(
382
386
  provider: str,
383
387
  model: str,
388
+ timeout: float | None = None,
384
389
  **kwargs: Any,
385
390
  ) -> LLMProvider:
386
391
  """Create an LLM provider.
@@ -388,9 +393,10 @@ def create_provider(
388
393
  Args:
389
394
  provider: Provider name (openai, anthropic, doubao, etc.)
390
395
  model: Model name
396
+ timeout: Request timeout in seconds (None = use provider default)
391
397
  **kwargs: Additional options
392
398
 
393
399
  Returns:
394
400
  LLMProvider instance
395
401
  """
396
- return ModelClientProvider(provider=provider, model=model, **kwargs)
402
+ return ModelClientProvider(provider=provider, model=model, timeout=timeout, **kwargs)