agnt5 0.2.6__cp39-abi3-macosx_11_0_arm64.whl → 0.2.7__cp39-abi3-macosx_11_0_arm64.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 +2 -1
- agnt5/_core.abi3.so +0 -0
- agnt5/agent.py +253 -83
- agnt5/entity.py +157 -5
- agnt5/worker.py +13 -11
- {agnt5-0.2.6.dist-info → agnt5-0.2.7.dist-info}/METADATA +1 -1
- {agnt5-0.2.6.dist-info → agnt5-0.2.7.dist-info}/RECORD +8 -8
- {agnt5-0.2.6.dist-info → agnt5-0.2.7.dist-info}/WHEEL +0 -0
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, Handoff, agent, handoff
|
|
9
|
+
from .agent import Agent, AgentContext, AgentRegistry, AgentResult, Handoff, agent, handoff
|
|
10
10
|
from .client import Client, RunError
|
|
11
11
|
from .context import Context
|
|
12
12
|
from .function import FunctionContext
|
|
@@ -46,6 +46,7 @@ __all__ = [
|
|
|
46
46
|
"Context",
|
|
47
47
|
"FunctionContext",
|
|
48
48
|
"WorkflowContext",
|
|
49
|
+
"AgentContext",
|
|
49
50
|
"Client",
|
|
50
51
|
"Worker",
|
|
51
52
|
"function",
|
agnt5/_core.abi3.so
CHANGED
|
Binary file
|
agnt5/agent.py
CHANGED
|
@@ -23,6 +23,175 @@ logger = setup_module_logger(__name__)
|
|
|
23
23
|
_AGENT_REGISTRY: Dict[str, "Agent"] = {}
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
class AgentContext(Context):
|
|
27
|
+
"""
|
|
28
|
+
Context for agent execution with conversation state management.
|
|
29
|
+
|
|
30
|
+
Extends base Context with:
|
|
31
|
+
- State management via EntityStateManager
|
|
32
|
+
- Conversation history persistence
|
|
33
|
+
- Context inheritance (child agents share parent's state)
|
|
34
|
+
|
|
35
|
+
Three initialization modes:
|
|
36
|
+
1. Standalone: Creates own state manager (playground testing)
|
|
37
|
+
2. Inherit WorkflowContext: Shares parent's state manager
|
|
38
|
+
3. Inherit parent AgentContext: Shares parent's state manager
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
```python
|
|
42
|
+
# Standalone agent with conversation history
|
|
43
|
+
ctx = AgentContext(run_id="session-1", agent_name="tutor")
|
|
44
|
+
result = await agent.run("Hello", context=ctx)
|
|
45
|
+
result = await agent.run("Continue", context=ctx) # Remembers previous message
|
|
46
|
+
|
|
47
|
+
# Agent in workflow - shares workflow state
|
|
48
|
+
@workflow
|
|
49
|
+
async def research_workflow(ctx: WorkflowContext):
|
|
50
|
+
agent_result = await research_agent.run("Find AI trends", context=ctx)
|
|
51
|
+
# Agent has access to workflow state via inherited context
|
|
52
|
+
```
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
run_id: str,
|
|
58
|
+
agent_name: str,
|
|
59
|
+
session_id: Optional[str] = None,
|
|
60
|
+
state_manager: Optional[Any] = None,
|
|
61
|
+
parent_context: Optional[Context] = None,
|
|
62
|
+
attempt: int = 0,
|
|
63
|
+
runtime_context: Optional[Any] = None,
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Initialize agent context.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
run_id: Unique execution identifier
|
|
70
|
+
agent_name: Name of the agent
|
|
71
|
+
session_id: Session identifier for conversation history (default: run_id)
|
|
72
|
+
state_manager: Optional state manager (for context inheritance)
|
|
73
|
+
parent_context: Parent context to inherit state from
|
|
74
|
+
attempt: Retry attempt number
|
|
75
|
+
runtime_context: RuntimeContext for trace correlation
|
|
76
|
+
"""
|
|
77
|
+
super().__init__(run_id, attempt, runtime_context)
|
|
78
|
+
|
|
79
|
+
self._agent_name = agent_name
|
|
80
|
+
self._session_id = session_id or run_id
|
|
81
|
+
|
|
82
|
+
# Determine state manager based on parent context
|
|
83
|
+
from .entity import EntityStateManager, _get_state_manager
|
|
84
|
+
|
|
85
|
+
if state_manager:
|
|
86
|
+
# Explicit state manager provided
|
|
87
|
+
self._state_manager = state_manager
|
|
88
|
+
logger.debug(f"AgentContext using provided state manager")
|
|
89
|
+
elif parent_context:
|
|
90
|
+
# Try to inherit state manager from parent
|
|
91
|
+
try:
|
|
92
|
+
# Check if parent is WorkflowContext or AgentContext
|
|
93
|
+
if hasattr(parent_context, '_workflow_entity'):
|
|
94
|
+
# WorkflowContext - get state manager from worker context
|
|
95
|
+
self._state_manager = _get_state_manager()
|
|
96
|
+
logger.debug(f"AgentContext inheriting state from WorkflowContext")
|
|
97
|
+
elif hasattr(parent_context, '_state_manager'):
|
|
98
|
+
# Parent AgentContext - share state manager
|
|
99
|
+
self._state_manager = parent_context._state_manager
|
|
100
|
+
logger.debug(f"AgentContext inheriting state from parent AgentContext")
|
|
101
|
+
else:
|
|
102
|
+
# FunctionContext or base Context - create new state manager
|
|
103
|
+
self._state_manager = EntityStateManager()
|
|
104
|
+
logger.debug(f"AgentContext created new state manager (parent has no state)")
|
|
105
|
+
except RuntimeError:
|
|
106
|
+
# _get_state_manager() failed (not in worker context) - create standalone
|
|
107
|
+
self._state_manager = EntityStateManager()
|
|
108
|
+
logger.debug(f"AgentContext created standalone state manager")
|
|
109
|
+
else:
|
|
110
|
+
# Standalone - create new state manager
|
|
111
|
+
self._state_manager = EntityStateManager()
|
|
112
|
+
logger.debug(f"AgentContext created standalone state manager")
|
|
113
|
+
|
|
114
|
+
# Conversation key for state storage
|
|
115
|
+
self._conversation_key = f"agent:{agent_name}:{self._session_id}:messages"
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def state(self):
|
|
119
|
+
"""
|
|
120
|
+
Get state interface for agent state management.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
EntityState instance for state operations
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
# Store conversation history
|
|
127
|
+
messages = ctx.state.get(f"agent:{agent_name}:{session_id}:messages", [])
|
|
128
|
+
messages.append({"role": "user", "content": "Hello"})
|
|
129
|
+
ctx.state.set(f"agent:{agent_name}:{session_id}:messages", messages)
|
|
130
|
+
|
|
131
|
+
# Store agent-specific data
|
|
132
|
+
ctx.state.set("research_results", data)
|
|
133
|
+
"""
|
|
134
|
+
from .entity import EntityState
|
|
135
|
+
|
|
136
|
+
# Use agent's conversation key as the state key
|
|
137
|
+
state_key = ("agent", self._conversation_key)
|
|
138
|
+
state_dict = self._state_manager.get_or_create_state(state_key)
|
|
139
|
+
return EntityState(state_dict)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def session_id(self) -> str:
|
|
143
|
+
"""Get session identifier for this agent context."""
|
|
144
|
+
return self._session_id
|
|
145
|
+
|
|
146
|
+
def get_conversation_history(self) -> List[Message]:
|
|
147
|
+
"""
|
|
148
|
+
Retrieve conversation history from state.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List of Message objects from conversation history
|
|
152
|
+
"""
|
|
153
|
+
messages_data = self.state.get(self._conversation_key, [])
|
|
154
|
+
|
|
155
|
+
# Convert dict representations back to Message objects
|
|
156
|
+
messages = []
|
|
157
|
+
for msg_dict in messages_data:
|
|
158
|
+
if isinstance(msg_dict, dict):
|
|
159
|
+
role = msg_dict.get("role", "user")
|
|
160
|
+
content = msg_dict.get("content", "")
|
|
161
|
+
if role == "user":
|
|
162
|
+
messages.append(Message.user(content))
|
|
163
|
+
elif role == "assistant":
|
|
164
|
+
messages.append(Message.assistant(content))
|
|
165
|
+
else:
|
|
166
|
+
# Generic message - create with MessageRole enum
|
|
167
|
+
from .lm import MessageRole
|
|
168
|
+
msg_role = MessageRole(role) if role in ("user", "assistant", "system") else MessageRole.USER
|
|
169
|
+
msg = Message(role=msg_role, content=content)
|
|
170
|
+
messages.append(msg)
|
|
171
|
+
else:
|
|
172
|
+
# Already a Message object
|
|
173
|
+
messages.append(msg_dict)
|
|
174
|
+
|
|
175
|
+
return messages
|
|
176
|
+
|
|
177
|
+
def save_conversation_history(self, messages: List[Message]) -> None:
|
|
178
|
+
"""
|
|
179
|
+
Save conversation history to state.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
messages: List of Message objects to persist
|
|
183
|
+
"""
|
|
184
|
+
# Convert Message objects to dict for JSON serialization
|
|
185
|
+
messages_data = []
|
|
186
|
+
for msg in messages:
|
|
187
|
+
messages_data.append({
|
|
188
|
+
"role": msg.role.value if hasattr(msg.role, 'value') else str(msg.role),
|
|
189
|
+
"content": msg.content
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
self.state.set(self._conversation_key, messages_data)
|
|
193
|
+
|
|
194
|
+
|
|
26
195
|
class Handoff:
|
|
27
196
|
"""Configuration for agent-to-agent handoff.
|
|
28
197
|
|
|
@@ -463,13 +632,47 @@ class Agent:
|
|
|
463
632
|
print(result.output)
|
|
464
633
|
```
|
|
465
634
|
"""
|
|
466
|
-
# Create
|
|
635
|
+
# Create or adapt context
|
|
467
636
|
if context is None:
|
|
637
|
+
# Standalone execution - create AgentContext
|
|
468
638
|
import uuid
|
|
469
|
-
|
|
470
|
-
context =
|
|
471
|
-
run_id=
|
|
639
|
+
run_id = f"agent-{self.name}-{uuid.uuid4().hex[:8]}"
|
|
640
|
+
context = AgentContext(
|
|
641
|
+
run_id=run_id,
|
|
642
|
+
agent_name=self.name,
|
|
643
|
+
)
|
|
644
|
+
elif isinstance(context, AgentContext):
|
|
645
|
+
# Already AgentContext - use as-is
|
|
646
|
+
pass
|
|
647
|
+
elif hasattr(context, '_workflow_entity'):
|
|
648
|
+
# WorkflowContext - create AgentContext that inherits state
|
|
649
|
+
import uuid
|
|
650
|
+
run_id = f"{context.run_id}:agent:{self.name}"
|
|
651
|
+
context = AgentContext(
|
|
652
|
+
run_id=run_id,
|
|
653
|
+
agent_name=self.name,
|
|
654
|
+
session_id=context.run_id, # Share workflow's session
|
|
655
|
+
parent_context=context,
|
|
472
656
|
)
|
|
657
|
+
else:
|
|
658
|
+
# FunctionContext or other - create new AgentContext
|
|
659
|
+
import uuid
|
|
660
|
+
run_id = f"{context.run_id}:agent:{self.name}"
|
|
661
|
+
context = AgentContext(
|
|
662
|
+
run_id=run_id,
|
|
663
|
+
agent_name=self.name,
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
# Load conversation history from state (if AgentContext)
|
|
667
|
+
if isinstance(context, AgentContext):
|
|
668
|
+
messages: List[Message] = context.get_conversation_history()
|
|
669
|
+
# Add new user message
|
|
670
|
+
messages.append(Message.user(user_message))
|
|
671
|
+
# Save updated conversation
|
|
672
|
+
context.save_conversation_history(messages)
|
|
673
|
+
else:
|
|
674
|
+
# Fallback for non-AgentContext (shouldn't happen with code above)
|
|
675
|
+
messages = [Message.user(user_message)]
|
|
473
676
|
|
|
474
677
|
# Create span for agent execution with trace linking
|
|
475
678
|
from ._core import create_span
|
|
@@ -480,12 +683,10 @@ class Agent:
|
|
|
480
683
|
context._runtime_context if hasattr(context, "_runtime_context") else None,
|
|
481
684
|
{
|
|
482
685
|
"agent.name": self.name,
|
|
483
|
-
"agent.model": self.
|
|
686
|
+
"agent.model": self.model_name, # Use model_name (always a string)
|
|
484
687
|
"agent.max_iterations": str(self.max_iterations),
|
|
485
688
|
},
|
|
486
689
|
) as span:
|
|
487
|
-
# Initialize conversation
|
|
488
|
-
messages: List[Message] = [Message.user(user_message)]
|
|
489
690
|
all_tool_calls: List[Dict[str, Any]] = []
|
|
490
691
|
|
|
491
692
|
# Reasoning loop
|
|
@@ -508,26 +709,42 @@ class Agent:
|
|
|
508
709
|
"content": msg.content
|
|
509
710
|
})
|
|
510
711
|
|
|
511
|
-
# Call LLM
|
|
512
|
-
#
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
request.config.
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
712
|
+
# Call LLM
|
|
713
|
+
# Check if we have a legacy LanguageModel instance or need to create one
|
|
714
|
+
if self._language_model is not None:
|
|
715
|
+
# Legacy API: use provided LanguageModel instance
|
|
716
|
+
request = GenerateRequest(
|
|
717
|
+
model="mock-model", # Not used by MockLanguageModel
|
|
718
|
+
system_prompt=self.instructions,
|
|
719
|
+
messages=messages,
|
|
720
|
+
tools=tool_defs if tool_defs else [],
|
|
721
|
+
)
|
|
722
|
+
request.config.temperature = self.temperature
|
|
723
|
+
if self.max_tokens:
|
|
724
|
+
request.config.max_tokens = self.max_tokens
|
|
725
|
+
if self.top_p:
|
|
726
|
+
request.config.top_p = self.top_p
|
|
727
|
+
response = await self._language_model.generate(request)
|
|
728
|
+
else:
|
|
729
|
+
# New API: model is a string, create internal LM instance
|
|
730
|
+
request = GenerateRequest(
|
|
731
|
+
model=self.model,
|
|
732
|
+
system_prompt=self.instructions,
|
|
733
|
+
messages=messages,
|
|
734
|
+
tools=tool_defs if tool_defs else [],
|
|
735
|
+
)
|
|
736
|
+
request.config.temperature = self.temperature
|
|
737
|
+
if self.max_tokens:
|
|
738
|
+
request.config.max_tokens = self.max_tokens
|
|
739
|
+
if self.top_p:
|
|
740
|
+
request.config.top_p = self.top_p
|
|
741
|
+
|
|
742
|
+
# Create internal LM instance for generation
|
|
743
|
+
# TODO: Use model_config when provided
|
|
744
|
+
from .lm import _LanguageModel
|
|
745
|
+
provider, model_name = self.model.split('/', 1)
|
|
746
|
+
internal_lm = _LanguageModel(provider=provider.lower(), default_model=None)
|
|
747
|
+
response = await internal_lm.generate(request)
|
|
531
748
|
|
|
532
749
|
# Add assistant response to messages
|
|
533
750
|
messages.append(Message.assistant(response.text))
|
|
@@ -576,6 +793,9 @@ class Agent:
|
|
|
576
793
|
f"Handoff detected to '{result['to_agent']}', "
|
|
577
794
|
f"terminating current agent"
|
|
578
795
|
)
|
|
796
|
+
# Save conversation before returning
|
|
797
|
+
if isinstance(context, AgentContext):
|
|
798
|
+
context.save_conversation_history(messages)
|
|
579
799
|
# Return immediately with handoff result
|
|
580
800
|
return AgentResult(
|
|
581
801
|
output=result["output"],
|
|
@@ -613,6 +833,9 @@ class Agent:
|
|
|
613
833
|
else:
|
|
614
834
|
# No tool calls - agent is done
|
|
615
835
|
self.logger.debug(f"Agent completed after {iteration + 1} iterations")
|
|
836
|
+
# Save conversation before returning
|
|
837
|
+
if isinstance(context, AgentContext):
|
|
838
|
+
context.save_conversation_history(messages)
|
|
616
839
|
return AgentResult(
|
|
617
840
|
output=response.text,
|
|
618
841
|
tool_calls=all_tool_calls,
|
|
@@ -622,68 +845,15 @@ class Agent:
|
|
|
622
845
|
# Max iterations reached
|
|
623
846
|
self.logger.warning(f"Agent reached max iterations ({self.max_iterations})")
|
|
624
847
|
final_output = messages[-1].content if messages else "No output generated"
|
|
848
|
+
# Save conversation before returning
|
|
849
|
+
if isinstance(context, AgentContext):
|
|
850
|
+
context.save_conversation_history(messages)
|
|
625
851
|
return AgentResult(
|
|
626
852
|
output=final_output,
|
|
627
853
|
tool_calls=all_tool_calls,
|
|
628
854
|
context=context,
|
|
629
855
|
)
|
|
630
856
|
|
|
631
|
-
async def chat(
|
|
632
|
-
self,
|
|
633
|
-
user_message: str,
|
|
634
|
-
messages: List[Message],
|
|
635
|
-
context: Optional[Context] = None,
|
|
636
|
-
) -> tuple[str, List[Message]]:
|
|
637
|
-
"""Continue multi-turn conversation.
|
|
638
|
-
|
|
639
|
-
Args:
|
|
640
|
-
user_message: New user message
|
|
641
|
-
messages: Previous conversation messages
|
|
642
|
-
context: Optional context
|
|
643
|
-
|
|
644
|
-
Returns:
|
|
645
|
-
Tuple of (assistant_response, updated_messages)
|
|
646
|
-
|
|
647
|
-
Example:
|
|
648
|
-
```python
|
|
649
|
-
messages = []
|
|
650
|
-
response, messages = await agent.chat("Hello", messages)
|
|
651
|
-
response, messages = await agent.chat("Tell me more", messages)
|
|
652
|
-
```
|
|
653
|
-
"""
|
|
654
|
-
if context is None:
|
|
655
|
-
import uuid
|
|
656
|
-
|
|
657
|
-
context = Context(
|
|
658
|
-
run_id=f"agent-chat-{self.name}-{uuid.uuid4().hex[:8]}",
|
|
659
|
-
)
|
|
660
|
-
|
|
661
|
-
# Add user message
|
|
662
|
-
conversation = messages + [Message.user(user_message)]
|
|
663
|
-
|
|
664
|
-
# Build request (no tools for simple chat)
|
|
665
|
-
request = GenerateRequest(
|
|
666
|
-
model=self.model,
|
|
667
|
-
system_prompt=self.instructions,
|
|
668
|
-
messages=conversation,
|
|
669
|
-
)
|
|
670
|
-
request.config.temperature = self.temperature
|
|
671
|
-
if self.max_tokens:
|
|
672
|
-
request.config.max_tokens = self.max_tokens
|
|
673
|
-
if self.top_p:
|
|
674
|
-
request.config.top_p = self.top_p
|
|
675
|
-
|
|
676
|
-
# Create internal LM instance for generation
|
|
677
|
-
from .lm import _LanguageModel
|
|
678
|
-
provider, model_name = self.model.split('/', 1)
|
|
679
|
-
internal_lm = _LanguageModel(provider=provider.lower(), default_model=None)
|
|
680
|
-
response = await internal_lm.generate(request)
|
|
681
|
-
|
|
682
|
-
# Add assistant response
|
|
683
|
-
conversation.append(Message.assistant(response.text))
|
|
684
|
-
|
|
685
|
-
return response.text, conversation
|
|
686
|
-
|
|
687
857
|
|
|
688
858
|
def agent(
|
|
689
859
|
_func: Optional[Callable] = None,
|
agnt5/entity.py
CHANGED
|
@@ -6,7 +6,8 @@ import asyncio
|
|
|
6
6
|
import contextvars
|
|
7
7
|
import functools
|
|
8
8
|
import inspect
|
|
9
|
-
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any, Dict, Optional, Tuple, get_type_hints
|
|
10
11
|
|
|
11
12
|
from ._schema_utils import extract_function_metadata, extract_function_schemas
|
|
12
13
|
from .exceptions import ExecutionError
|
|
@@ -230,6 +231,108 @@ def create_entity_context():
|
|
|
230
231
|
return manager, token
|
|
231
232
|
|
|
232
233
|
|
|
234
|
+
def extract_state_schema(entity_class: type) -> Optional[Dict[str, Any]]:
|
|
235
|
+
"""
|
|
236
|
+
Extract JSON schema from entity class for state structure documentation.
|
|
237
|
+
|
|
238
|
+
The schema can be provided in multiple ways (in order of preference):
|
|
239
|
+
1. Explicit _state_schema class attribute (most explicit)
|
|
240
|
+
2. Docstring with state description
|
|
241
|
+
3. Type annotations on __init__ method (least explicit, basic types only)
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
entity_class: The Entity subclass to extract schema from
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
JSON schema dict or None if no schema could be extracted
|
|
248
|
+
|
|
249
|
+
Examples:
|
|
250
|
+
# Option 1: Explicit schema (recommended)
|
|
251
|
+
class ShoppingCart(Entity):
|
|
252
|
+
_state_schema = {
|
|
253
|
+
"type": "object",
|
|
254
|
+
"properties": {
|
|
255
|
+
"items": {"type": "array", "description": "Cart items"},
|
|
256
|
+
"total": {"type": "number", "description": "Cart total"}
|
|
257
|
+
},
|
|
258
|
+
"description": "Shopping cart state"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# Option 2: Docstring
|
|
262
|
+
class ShoppingCart(Entity):
|
|
263
|
+
'''
|
|
264
|
+
Shopping cart entity.
|
|
265
|
+
|
|
266
|
+
State:
|
|
267
|
+
items (list): List of cart items
|
|
268
|
+
total (float): Total cart value
|
|
269
|
+
'''
|
|
270
|
+
|
|
271
|
+
# Option 3: Type hints (basic extraction)
|
|
272
|
+
class ShoppingCart(Entity):
|
|
273
|
+
def __init__(self, key: str):
|
|
274
|
+
super().__init__(key)
|
|
275
|
+
self.items: list = []
|
|
276
|
+
self.total: float = 0.0
|
|
277
|
+
"""
|
|
278
|
+
# Option 1: Check for explicit _state_schema attribute
|
|
279
|
+
if hasattr(entity_class, '_state_schema'):
|
|
280
|
+
schema = entity_class._state_schema
|
|
281
|
+
logger.debug(f"Found explicit _state_schema for {entity_class.__name__}")
|
|
282
|
+
return schema
|
|
283
|
+
|
|
284
|
+
# Option 2: Extract from docstring (basic parsing)
|
|
285
|
+
if entity_class.__doc__:
|
|
286
|
+
doc = entity_class.__doc__.strip()
|
|
287
|
+
if "State:" in doc or "state:" in doc.lower():
|
|
288
|
+
# Found state documentation - create basic schema
|
|
289
|
+
logger.debug(f"Found state documentation in docstring for {entity_class.__name__}")
|
|
290
|
+
return {
|
|
291
|
+
"type": "object",
|
|
292
|
+
"description": f"State structure for {entity_class.__name__} (see docstring for details)"
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# Option 3: Try to extract from __init__ type hints (very basic)
|
|
296
|
+
try:
|
|
297
|
+
init_method = entity_class.__init__
|
|
298
|
+
type_hints = get_type_hints(init_method)
|
|
299
|
+
# Remove 'key' and 'return' from hints
|
|
300
|
+
state_hints = {k: v for k, v in type_hints.items() if k not in ('key', 'return')}
|
|
301
|
+
|
|
302
|
+
if state_hints:
|
|
303
|
+
logger.debug(f"Extracted type hints from __init__ for {entity_class.__name__}")
|
|
304
|
+
properties = {}
|
|
305
|
+
for name, type_hint in state_hints.items():
|
|
306
|
+
# Basic type mapping
|
|
307
|
+
if type_hint == str:
|
|
308
|
+
properties[name] = {"type": "string"}
|
|
309
|
+
elif type_hint == int:
|
|
310
|
+
properties[name] = {"type": "integer"}
|
|
311
|
+
elif type_hint == float:
|
|
312
|
+
properties[name] = {"type": "number"}
|
|
313
|
+
elif type_hint == bool:
|
|
314
|
+
properties[name] = {"type": "boolean"}
|
|
315
|
+
elif type_hint == list or str(type_hint).startswith('list'):
|
|
316
|
+
properties[name] = {"type": "array"}
|
|
317
|
+
elif type_hint == dict or str(type_hint).startswith('dict'):
|
|
318
|
+
properties[name] = {"type": "object"}
|
|
319
|
+
else:
|
|
320
|
+
properties[name] = {"type": "object", "description": str(type_hint)}
|
|
321
|
+
|
|
322
|
+
if properties:
|
|
323
|
+
return {
|
|
324
|
+
"type": "object",
|
|
325
|
+
"properties": properties,
|
|
326
|
+
"description": f"State structure inferred from type hints for {entity_class.__name__}"
|
|
327
|
+
}
|
|
328
|
+
except Exception as e:
|
|
329
|
+
logger.debug(f"Could not extract type hints from {entity_class.__name__}: {e}")
|
|
330
|
+
|
|
331
|
+
# No schema could be extracted
|
|
332
|
+
logger.debug(f"No state schema found for {entity_class.__name__}")
|
|
333
|
+
return None
|
|
334
|
+
|
|
335
|
+
|
|
233
336
|
class EntityRegistry:
|
|
234
337
|
"""Registry for entity types."""
|
|
235
338
|
|
|
@@ -262,7 +365,7 @@ class EntityType:
|
|
|
262
365
|
"""
|
|
263
366
|
Metadata about an Entity class.
|
|
264
367
|
|
|
265
|
-
Stores entity name, method schemas, and metadata for Worker auto-discovery
|
|
368
|
+
Stores entity name, method schemas, state schema, and metadata for Worker auto-discovery
|
|
266
369
|
and platform integration. Created automatically when Entity subclasses are defined.
|
|
267
370
|
"""
|
|
268
371
|
|
|
@@ -278,8 +381,50 @@ class EntityType:
|
|
|
278
381
|
self.entity_class = entity_class
|
|
279
382
|
self._method_schemas: Dict[str, Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]] = {}
|
|
280
383
|
self._method_metadata: Dict[str, Dict[str, str]] = {}
|
|
384
|
+
self._state_schema: Optional[Dict[str, Any]] = None
|
|
281
385
|
logger.debug("Created entity type: %s", name)
|
|
282
386
|
|
|
387
|
+
def set_state_schema(self, schema: Optional[Dict[str, Any]]) -> None:
|
|
388
|
+
"""
|
|
389
|
+
Set the state schema for this entity type.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
schema: JSON schema describing the entity's state structure
|
|
393
|
+
"""
|
|
394
|
+
self._state_schema = schema
|
|
395
|
+
if schema:
|
|
396
|
+
logger.debug(f"Set state schema for {self.name}")
|
|
397
|
+
|
|
398
|
+
def build_entity_definition(self) -> Dict[str, Any]:
|
|
399
|
+
"""
|
|
400
|
+
Build complete entity definition for platform registration.
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
Dictionary with entity name, state schema, and method schemas
|
|
404
|
+
"""
|
|
405
|
+
# Build method schemas dict
|
|
406
|
+
method_schemas = {}
|
|
407
|
+
for method_name, (input_schema, output_schema) in self._method_schemas.items():
|
|
408
|
+
method_metadata = self._method_metadata.get(method_name, {})
|
|
409
|
+
method_schemas[method_name] = {
|
|
410
|
+
"input_schema": input_schema,
|
|
411
|
+
"output_schema": output_schema,
|
|
412
|
+
"description": method_metadata.get("description", ""),
|
|
413
|
+
"metadata": method_metadata
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
# Build complete definition
|
|
417
|
+
definition = {
|
|
418
|
+
"entity_name": self.name,
|
|
419
|
+
"methods": method_schemas
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
# Add state schema if available
|
|
423
|
+
if self._state_schema:
|
|
424
|
+
definition["state_schema"] = self._state_schema
|
|
425
|
+
|
|
426
|
+
return definition
|
|
427
|
+
|
|
283
428
|
|
|
284
429
|
# ============================================================================
|
|
285
430
|
# Class-Based Entity API (Cloudflare Durable Objects style)
|
|
@@ -507,9 +652,10 @@ class Entity:
|
|
|
507
652
|
Auto-register Entity subclasses and wrap methods.
|
|
508
653
|
|
|
509
654
|
This is called automatically when a class inherits from Entity.
|
|
510
|
-
It performs
|
|
511
|
-
1.
|
|
512
|
-
2.
|
|
655
|
+
It performs three tasks:
|
|
656
|
+
1. Extracts state schema from the class
|
|
657
|
+
2. Wraps all public async methods with single-writer consistency
|
|
658
|
+
3. Registers the entity type with metadata for platform discovery
|
|
513
659
|
"""
|
|
514
660
|
super().__init_subclass__(**kwargs)
|
|
515
661
|
|
|
@@ -524,6 +670,12 @@ class Entity:
|
|
|
524
670
|
# Create an EntityType for this class, storing the class reference
|
|
525
671
|
entity_type = EntityType(cls.__name__, entity_class=cls)
|
|
526
672
|
|
|
673
|
+
# Extract and set state schema
|
|
674
|
+
state_schema = extract_state_schema(cls)
|
|
675
|
+
if state_schema:
|
|
676
|
+
entity_type.set_state_schema(state_schema)
|
|
677
|
+
logger.debug(f"Extracted state schema for {cls.__name__}")
|
|
678
|
+
|
|
527
679
|
# Wrap all public async methods and register them
|
|
528
680
|
for name, method in inspect.getmembers(cls, predicate=inspect.iscoroutinefunction):
|
|
529
681
|
if not name.startswith('_'):
|
agnt5/worker.py
CHANGED
|
@@ -484,18 +484,13 @@ class Worker:
|
|
|
484
484
|
logger.warning(f"Entity '{entity_class.__name__}' not found in EntityRegistry")
|
|
485
485
|
continue
|
|
486
486
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
method_schemas[method_name] = {
|
|
491
|
-
"input_schema": input_schema,
|
|
492
|
-
"output_schema": output_schema,
|
|
493
|
-
"metadata": method_metadata
|
|
494
|
-
}
|
|
487
|
+
# Build complete entity definition with state schema and method schemas
|
|
488
|
+
entity_definition = entity_type.build_entity_definition()
|
|
489
|
+
definition_str = json.dumps(entity_definition)
|
|
495
490
|
|
|
491
|
+
# Keep minimal metadata for backward compatibility
|
|
496
492
|
metadata_dict = {
|
|
497
493
|
"methods": json.dumps(list(entity_type._method_schemas.keys())),
|
|
498
|
-
"method_schemas": json.dumps(method_schemas)
|
|
499
494
|
}
|
|
500
495
|
|
|
501
496
|
component_info = self._PyComponentInfo(
|
|
@@ -503,14 +498,21 @@ class Worker:
|
|
|
503
498
|
component_type="entity",
|
|
504
499
|
metadata=metadata_dict,
|
|
505
500
|
config={},
|
|
506
|
-
input_schema=None,
|
|
501
|
+
input_schema=None, # Entities don't have single input/output schemas
|
|
507
502
|
output_schema=None,
|
|
508
|
-
definition=
|
|
503
|
+
definition=definition_str, # Complete entity definition with state and methods
|
|
509
504
|
)
|
|
510
505
|
components.append(component_info)
|
|
506
|
+
logger.debug(f"Registered entity '{entity_type.name}' with definition")
|
|
511
507
|
|
|
512
508
|
# Process agents
|
|
509
|
+
from .agent import AgentRegistry
|
|
510
|
+
|
|
513
511
|
for agent in self._explicit_components['agents']:
|
|
512
|
+
# Register agent in AgentRegistry for execution lookup
|
|
513
|
+
AgentRegistry.register(agent)
|
|
514
|
+
logger.debug(f"Registered agent '{agent.name}' in AgentRegistry for execution")
|
|
515
|
+
|
|
514
516
|
input_schema_str = json.dumps(agent.input_schema) if hasattr(agent, 'input_schema') and agent.input_schema else None
|
|
515
517
|
output_schema_str = json.dumps(agent.output_schema) if hasattr(agent, 'output_schema') and agent.output_schema else None
|
|
516
518
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
agnt5-0.2.
|
|
2
|
-
agnt5-0.2.
|
|
3
|
-
agnt5/__init__.py,sha256=
|
|
1
|
+
agnt5-0.2.7.dist-info/METADATA,sha256=9HBILOmv68LG9Eskgr1NkarZOxD8rGrox-MN73SP3pU,994
|
|
2
|
+
agnt5-0.2.7.dist-info/WHEEL,sha256=vpqC0tRn_8bTHidvtrPbrnFQPZnrhuKzsjDdeKwCd58,102
|
|
3
|
+
agnt5/__init__.py,sha256=Cscbhye6pA8Jp-sKBGfP4kYXElUdF6aOSHVf-7ph4Dg,2045
|
|
4
4
|
agnt5/_compat.py,sha256=BGuy3v5VDOHVa5f3Z-C22iMN19lAt0mPmXwF3qSSWxI,369
|
|
5
|
-
agnt5/_core.abi3.so,sha256=
|
|
5
|
+
agnt5/_core.abi3.so,sha256=tTdtX6Yqrub5DnwZXMCrWz5fkw1zhfUUZdzq6FXca5Y,11970288
|
|
6
6
|
agnt5/_retry_utils.py,sha256=loHsWY5BR4wZy57IzcDEjQAy88DHVwVIr25Cn1d9GPA,5801
|
|
7
7
|
agnt5/_schema_utils.py,sha256=MR67RW757T4Oq2Jqf4kB61H_b51zwaf3CLWELnkngRo,9572
|
|
8
8
|
agnt5/_telemetry.py,sha256=bIY9AvBRjJBTHoBPbfR6X1OgaiUf-T0vCoi0_snsWXA,5957
|
|
9
|
-
agnt5/agent.py,sha256=
|
|
9
|
+
agnt5/agent.py,sha256=cz9gDB6c-eRJhBihEIuvTnNBwonpH6G8lbm44j_DKgA,36704
|
|
10
10
|
agnt5/client.py,sha256=kXksazgxdVXWaG9OkjJA4cWruNtcS-ENhtnkrIdw-Nk,23212
|
|
11
11
|
agnt5/context.py,sha256=S2OzPkhn_jnqSWfT21mSYOux8vHaLKQxcAvggZDHQek,2378
|
|
12
|
-
agnt5/entity.py,sha256=
|
|
12
|
+
agnt5/entity.py,sha256=jMnSRTv6MNlK05cJ0FWYQR89ZTz_ywtVuwv-Sjr2Jfc,24925
|
|
13
13
|
agnt5/exceptions.py,sha256=mZ0q-NK6OKhYxgwBJpIbgpgzk-CJaFIHDbp1EE-pS7I,925
|
|
14
14
|
agnt5/function.py,sha256=f1vaAlJRwuo8cxCOGEd8XPido00mOhlPS8UJJx-6hJI,11041
|
|
15
15
|
agnt5/lm.py,sha256=9dFjd6eQ3f3lFZe7H7rWZherYiP_58MT1F5xpwD8PCg,23195
|
|
@@ -17,6 +17,6 @@ agnt5/tool.py,sha256=uc4L-Q9QyLzQDe-MZKk2Wo3o5e-mK8tfaQwVDgQdouQ,13133
|
|
|
17
17
|
agnt5/tracing.py,sha256=Mh2-OfnQM61lM_P8gxJstafdsUA8Gxoo1lP-Joxhub8,5980
|
|
18
18
|
agnt5/types.py,sha256=Zb71ZMwvrt1p4SH18cAKunp2y5tao_W5_jGYaPDejQo,2840
|
|
19
19
|
agnt5/version.py,sha256=rOq1mObLihnnKgKqBrwZA0zwOPudEKVFcW1a48ynkqc,573
|
|
20
|
-
agnt5/worker.py,sha256=
|
|
20
|
+
agnt5/worker.py,sha256=fWC_N2yP5BlE0W7p0MXFfnQH5OBMU94lyEuImOhBwQI,46470
|
|
21
21
|
agnt5/workflow.py,sha256=sU8Gk7unxE_Gla7Fe-KlXfcBvYa2326GciuoR26CCr0,19585
|
|
22
|
-
agnt5-0.2.
|
|
22
|
+
agnt5-0.2.7.dist-info/RECORD,,
|
|
File without changes
|