agnt5 0.2.8a4__cp310-abi3-manylinux_2_34_aarch64.whl → 0.2.8a5__cp310-abi3-manylinux_2_34_aarch64.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 +5 -1
- agnt5/_core.abi3.so +0 -0
- agnt5/agent.py +3 -2
- agnt5/exceptions.py +43 -0
- agnt5/tool.py +149 -0
- agnt5/worker.py +85 -2
- agnt5/workflow.py +109 -1
- {agnt5-0.2.8a4.dist-info → agnt5-0.2.8a5.dist-info}/METADATA +1 -1
- {agnt5-0.2.8a4.dist-info → agnt5-0.2.8a5.dist-info}/RECORD +10 -10
- {agnt5-0.2.8a4.dist-info → agnt5-0.2.8a5.dist-info}/WHEEL +0 -0
agnt5/__init__.py
CHANGED
|
@@ -26,9 +26,10 @@ from .exceptions import (
|
|
|
26
26
|
ExecutionError,
|
|
27
27
|
RetryError,
|
|
28
28
|
StateError,
|
|
29
|
+
WaitingForUserInputException,
|
|
29
30
|
)
|
|
30
31
|
from .function import FunctionRegistry, function
|
|
31
|
-
from .tool import Tool, ToolRegistry, tool
|
|
32
|
+
from .tool import AskUserTool, RequestApprovalTool, Tool, ToolRegistry, tool
|
|
32
33
|
from .types import BackoffPolicy, BackoffType, FunctionConfig, RetryPolicy, WorkflowConfig
|
|
33
34
|
from .version import _get_version
|
|
34
35
|
from .worker import Worker
|
|
@@ -62,6 +63,8 @@ __all__ = [
|
|
|
62
63
|
"tool",
|
|
63
64
|
"Tool",
|
|
64
65
|
"ToolRegistry",
|
|
66
|
+
"AskUserTool",
|
|
67
|
+
"RequestApprovalTool",
|
|
65
68
|
"agent",
|
|
66
69
|
"Agent",
|
|
67
70
|
"AgentRegistry",
|
|
@@ -81,6 +84,7 @@ __all__ = [
|
|
|
81
84
|
"RetryError",
|
|
82
85
|
"StateError",
|
|
83
86
|
"CheckpointError",
|
|
87
|
+
"WaitingForUserInputException",
|
|
84
88
|
"RunError",
|
|
85
89
|
# Language Model (Simplified API)
|
|
86
90
|
"lm",
|
agnt5/_core.abi3.so
CHANGED
|
Binary file
|
agnt5/agent.py
CHANGED
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
import functools
|
|
10
10
|
import json
|
|
11
11
|
import logging
|
|
12
|
+
import time
|
|
12
13
|
from typing import Any, Callable, Dict, List, Optional
|
|
13
14
|
|
|
14
15
|
from .context import Context
|
|
@@ -221,10 +222,10 @@ class AgentContext(Context):
|
|
|
221
222
|
for msg in messages:
|
|
222
223
|
messages_data.append({
|
|
223
224
|
"role": msg.role.value if hasattr(msg.role, 'value') else str(msg.role),
|
|
224
|
-
"content": msg.content
|
|
225
|
+
"content": msg.content,
|
|
226
|
+
"timestamp": time.time() # Add timestamp for each message
|
|
225
227
|
})
|
|
226
228
|
|
|
227
|
-
import time
|
|
228
229
|
entity_type = "AgentSession"
|
|
229
230
|
entity_key = self._entity_key
|
|
230
231
|
|
agnt5/exceptions.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""AGNT5 SDK exceptions and error types."""
|
|
2
2
|
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
class AGNT5Error(Exception):
|
|
5
7
|
"""Base exception for all AGNT5 SDK errors."""
|
|
@@ -44,3 +46,44 @@ class NotImplementedError(AGNT5Error):
|
|
|
44
46
|
"""Raised when a feature is not yet implemented."""
|
|
45
47
|
|
|
46
48
|
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class WaitingForUserInputException(AGNT5Error):
|
|
52
|
+
"""Raised when workflow needs to pause for user input.
|
|
53
|
+
|
|
54
|
+
This exception is used internally by ctx.wait_for_user() to signal
|
|
55
|
+
that a workflow execution should pause and wait for user input.
|
|
56
|
+
|
|
57
|
+
The platform catches this exception and:
|
|
58
|
+
1. Saves the workflow checkpoint state
|
|
59
|
+
2. Returns awaiting_user_input status to the client
|
|
60
|
+
3. Presents the question and options to the user
|
|
61
|
+
4. Resumes execution when user responds
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
question: The question to ask the user
|
|
65
|
+
input_type: Type of input ("text", "approval", or "choice")
|
|
66
|
+
options: List of options for approval/choice inputs
|
|
67
|
+
checkpoint_state: Current workflow state for resume
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
question: str,
|
|
73
|
+
input_type: str,
|
|
74
|
+
options: Optional[List[Dict]],
|
|
75
|
+
checkpoint_state: Dict,
|
|
76
|
+
) -> None:
|
|
77
|
+
"""Initialize WaitingForUserInputException.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
question: Question to ask the user
|
|
81
|
+
input_type: Type of input - "text", "approval", or "choice"
|
|
82
|
+
options: List of option dicts (for approval/choice)
|
|
83
|
+
checkpoint_state: Workflow state snapshot for resume
|
|
84
|
+
"""
|
|
85
|
+
super().__init__(f"Waiting for user input: {question}")
|
|
86
|
+
self.question = question
|
|
87
|
+
self.input_type = input_type
|
|
88
|
+
self.options = options or []
|
|
89
|
+
self.checkpoint_state = checkpoint_state
|
agnt5/tool.py
CHANGED
|
@@ -416,3 +416,152 @@ def tool(
|
|
|
416
416
|
if _func is None:
|
|
417
417
|
return decorator
|
|
418
418
|
return decorator(_func)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
# ============================================================================
|
|
422
|
+
# Built-in Human-in-the-Loop Tools
|
|
423
|
+
# ============================================================================
|
|
424
|
+
|
|
425
|
+
class AskUserTool(Tool):
|
|
426
|
+
"""
|
|
427
|
+
Built-in tool that agents can use to request text input from users.
|
|
428
|
+
|
|
429
|
+
This tool pauses the workflow execution and waits for the user to provide
|
|
430
|
+
a text response. The workflow resumes when the user submits their input.
|
|
431
|
+
|
|
432
|
+
Example:
|
|
433
|
+
```python
|
|
434
|
+
from agnt5 import Agent, workflow, WorkflowContext
|
|
435
|
+
from agnt5.tool import AskUserTool
|
|
436
|
+
|
|
437
|
+
@workflow(chat=True)
|
|
438
|
+
async def agent_with_hitl(ctx: WorkflowContext, query: str) -> dict:
|
|
439
|
+
agent = Agent(
|
|
440
|
+
name="research_agent",
|
|
441
|
+
model="openai/gpt-4o-mini",
|
|
442
|
+
instructions="You are a research assistant.",
|
|
443
|
+
tools=[AskUserTool(ctx)]
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
result = await agent.run(query, context=ctx)
|
|
447
|
+
return {"response": result.output}
|
|
448
|
+
```
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
def __init__(self, context: "WorkflowContext"): # type: ignore
|
|
452
|
+
"""
|
|
453
|
+
Initialize AskUserTool.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
context: Workflow context with wait_for_user capability
|
|
457
|
+
"""
|
|
458
|
+
# Import here to avoid circular dependency
|
|
459
|
+
from .workflow import WorkflowContext
|
|
460
|
+
|
|
461
|
+
if not isinstance(context, WorkflowContext):
|
|
462
|
+
raise ConfigurationError(
|
|
463
|
+
"AskUserTool requires a WorkflowContext. "
|
|
464
|
+
"This tool can only be used within workflows."
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
super().__init__(
|
|
468
|
+
name="ask_user",
|
|
469
|
+
description="Ask the user a question and wait for their text response",
|
|
470
|
+
handler=self._handler,
|
|
471
|
+
auto_schema=True
|
|
472
|
+
)
|
|
473
|
+
self.context = context
|
|
474
|
+
|
|
475
|
+
async def _handler(self, ctx: Context, question: str) -> str:
|
|
476
|
+
"""
|
|
477
|
+
Ask user a question and wait for their response.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
ctx: Execution context (unused, required by Tool signature)
|
|
481
|
+
question: Question to ask the user
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
User's text response
|
|
485
|
+
"""
|
|
486
|
+
return await self.context.wait_for_user(question, input_type="text")
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
class RequestApprovalTool(Tool):
|
|
490
|
+
"""
|
|
491
|
+
Built-in tool that agents can use to request approval from users.
|
|
492
|
+
|
|
493
|
+
This tool pauses the workflow execution and presents an approval request
|
|
494
|
+
to the user with approve/reject options. The workflow resumes when the
|
|
495
|
+
user makes a decision.
|
|
496
|
+
|
|
497
|
+
Example:
|
|
498
|
+
```python
|
|
499
|
+
from agnt5 import Agent, workflow, WorkflowContext
|
|
500
|
+
from agnt5.tool import RequestApprovalTool
|
|
501
|
+
|
|
502
|
+
@workflow(chat=True)
|
|
503
|
+
async def deployment_agent(ctx: WorkflowContext, changes: dict) -> dict:
|
|
504
|
+
agent = Agent(
|
|
505
|
+
name="deploy_agent",
|
|
506
|
+
model="openai/gpt-4o-mini",
|
|
507
|
+
instructions="You help deploy code changes safely.",
|
|
508
|
+
tools=[RequestApprovalTool(ctx)]
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
result = await agent.run(
|
|
512
|
+
f"Review and deploy these changes: {changes}",
|
|
513
|
+
context=ctx
|
|
514
|
+
)
|
|
515
|
+
return {"response": result.output}
|
|
516
|
+
```
|
|
517
|
+
"""
|
|
518
|
+
|
|
519
|
+
def __init__(self, context: "WorkflowContext"): # type: ignore
|
|
520
|
+
"""
|
|
521
|
+
Initialize RequestApprovalTool.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
context: Workflow context with wait_for_user capability
|
|
525
|
+
"""
|
|
526
|
+
# Import here to avoid circular dependency
|
|
527
|
+
from .workflow import WorkflowContext
|
|
528
|
+
|
|
529
|
+
if not isinstance(context, WorkflowContext):
|
|
530
|
+
raise ConfigurationError(
|
|
531
|
+
"RequestApprovalTool requires a WorkflowContext. "
|
|
532
|
+
"This tool can only be used within workflows."
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
super().__init__(
|
|
536
|
+
name="request_approval",
|
|
537
|
+
description="Request user approval for an action before proceeding",
|
|
538
|
+
handler=self._handler,
|
|
539
|
+
auto_schema=True
|
|
540
|
+
)
|
|
541
|
+
self.context = context
|
|
542
|
+
|
|
543
|
+
async def _handler(self, ctx: Context, action: str, details: str = "") -> str:
|
|
544
|
+
"""
|
|
545
|
+
Request approval from user for an action.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
ctx: Execution context (unused, required by Tool signature)
|
|
549
|
+
action: The action requiring approval
|
|
550
|
+
details: Additional details about the action
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
"approve" or "reject" based on user's decision
|
|
554
|
+
"""
|
|
555
|
+
question = f"Action: {action}"
|
|
556
|
+
if details:
|
|
557
|
+
question += f"\n\nDetails:\n{details}"
|
|
558
|
+
question += "\n\nDo you approve?"
|
|
559
|
+
|
|
560
|
+
return await self.context.wait_for_user(
|
|
561
|
+
question,
|
|
562
|
+
input_type="approval",
|
|
563
|
+
options=[
|
|
564
|
+
{"id": "approve", "label": "Approve"},
|
|
565
|
+
{"id": "reject", "label": "Reject"}
|
|
566
|
+
]
|
|
567
|
+
)
|
agnt5/worker.py
CHANGED
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import contextvars
|
|
7
7
|
import logging
|
|
8
|
+
import uuid
|
|
8
9
|
from typing import Any, Dict, List, Optional
|
|
9
10
|
|
|
10
11
|
from .function import FunctionRegistry
|
|
@@ -758,16 +759,31 @@ class Worker:
|
|
|
758
759
|
"""Execute a workflow handler with automatic replay support."""
|
|
759
760
|
import json
|
|
760
761
|
from .workflow import WorkflowEntity, WorkflowContext
|
|
761
|
-
from .entity import _get_state_adapter
|
|
762
|
+
from .entity import _get_state_adapter, _entity_state_adapter_ctx
|
|
763
|
+
from .exceptions import WaitingForUserInputException
|
|
762
764
|
from ._core import PyExecuteComponentResponse
|
|
763
765
|
|
|
766
|
+
# Set entity state adapter in context so workflows can use Entities
|
|
767
|
+
_entity_state_adapter_ctx.set(self._entity_state_adapter)
|
|
768
|
+
|
|
764
769
|
try:
|
|
765
770
|
# Parse input data
|
|
766
771
|
input_dict = json.loads(input_data.decode("utf-8")) if input_data else {}
|
|
767
772
|
|
|
773
|
+
# Extract or generate session_id for multi-turn conversation support (for chat workflows)
|
|
774
|
+
# If session_id is provided, the workflow can maintain conversation context
|
|
775
|
+
session_id = input_dict.get("session_id")
|
|
776
|
+
|
|
777
|
+
if not session_id:
|
|
778
|
+
session_id = str(uuid.uuid4())
|
|
779
|
+
logger.info(f"Created new workflow session: {session_id}")
|
|
780
|
+
else:
|
|
781
|
+
logger.info(f"Using existing workflow session: {session_id}")
|
|
782
|
+
|
|
768
783
|
# Parse replay data from request metadata for crash recovery
|
|
769
784
|
completed_steps = {}
|
|
770
785
|
initial_state = {}
|
|
786
|
+
user_response = None
|
|
771
787
|
|
|
772
788
|
if hasattr(request, 'metadata') and request.metadata:
|
|
773
789
|
# Parse completed steps for replay
|
|
@@ -790,6 +806,11 @@ class Worker:
|
|
|
790
806
|
except json.JSONDecodeError:
|
|
791
807
|
logger.warning("Failed to parse workflow_state from metadata")
|
|
792
808
|
|
|
809
|
+
# Check for user response (workflow resume after pause)
|
|
810
|
+
if "user_response" in request.metadata:
|
|
811
|
+
user_response = request.metadata["user_response"]
|
|
812
|
+
logger.info(f"▶️ Resuming workflow with user response: {user_response}")
|
|
813
|
+
|
|
793
814
|
# Create WorkflowEntity for state management
|
|
794
815
|
workflow_entity = WorkflowEntity(run_id=f"{self.service_name}:{config.name}")
|
|
795
816
|
|
|
@@ -798,6 +819,11 @@ class Worker:
|
|
|
798
819
|
workflow_entity._completed_steps = completed_steps
|
|
799
820
|
logger.debug(f"Loaded {len(completed_steps)} completed steps into workflow entity")
|
|
800
821
|
|
|
822
|
+
# Inject user response if resuming from pause
|
|
823
|
+
if user_response:
|
|
824
|
+
workflow_entity.inject_user_response(user_response)
|
|
825
|
+
logger.debug(f"Injected user response into workflow entity")
|
|
826
|
+
|
|
801
827
|
if initial_state:
|
|
802
828
|
# Load initial state into entity's state adapter
|
|
803
829
|
state_adapter = _get_state_adapter()
|
|
@@ -860,13 +886,70 @@ class Worker:
|
|
|
860
886
|
|
|
861
887
|
logger.info(f"Workflow completed successfully with {len(step_events)} steps")
|
|
862
888
|
|
|
889
|
+
# Add session_id to metadata for multi-turn conversation support
|
|
890
|
+
metadata["session_id"] = session_id
|
|
891
|
+
|
|
863
892
|
return PyExecuteComponentResponse(
|
|
864
893
|
invocation_id=request.invocation_id,
|
|
865
894
|
success=True,
|
|
866
895
|
output_data=output_data,
|
|
867
896
|
state_update=None, # Not used for workflows (use metadata instead)
|
|
868
897
|
error_message=None,
|
|
869
|
-
metadata=metadata if metadata else None, # Include step events + state
|
|
898
|
+
metadata=metadata if metadata else None, # Include step events + state + session_id
|
|
899
|
+
is_chunk=False,
|
|
900
|
+
done=True,
|
|
901
|
+
chunk_index=0,
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
except WaitingForUserInputException as e:
|
|
905
|
+
# Workflow paused for user input
|
|
906
|
+
logger.info(f"⏸️ Workflow paused waiting for user input: {e.question}")
|
|
907
|
+
|
|
908
|
+
# Collect metadata for pause state
|
|
909
|
+
# Note: All metadata values must be strings for Rust FFI
|
|
910
|
+
pause_metadata = {
|
|
911
|
+
"status": "awaiting_user_input",
|
|
912
|
+
"question": e.question,
|
|
913
|
+
"input_type": e.input_type,
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
# Add optional fields only if they exist
|
|
917
|
+
if e.options:
|
|
918
|
+
pause_metadata["options"] = json.dumps(e.options)
|
|
919
|
+
if e.checkpoint_state:
|
|
920
|
+
pause_metadata["checkpoint_state"] = json.dumps(e.checkpoint_state)
|
|
921
|
+
if session_id:
|
|
922
|
+
pause_metadata["session_id"] = session_id
|
|
923
|
+
|
|
924
|
+
# Add step events to pause metadata for durability
|
|
925
|
+
step_events = ctx._workflow_entity._step_events
|
|
926
|
+
if step_events:
|
|
927
|
+
pause_metadata["step_events"] = json.dumps(step_events)
|
|
928
|
+
logger.debug(f"Paused workflow has {len(step_events)} recorded steps")
|
|
929
|
+
|
|
930
|
+
# Add current workflow state to pause metadata
|
|
931
|
+
if hasattr(ctx, '_workflow_entity') and ctx._workflow_entity._state is not None:
|
|
932
|
+
if ctx._workflow_entity._state.has_changes():
|
|
933
|
+
state_snapshot = ctx._workflow_entity._state.get_state_snapshot()
|
|
934
|
+
pause_metadata["workflow_state"] = json.dumps(state_snapshot)
|
|
935
|
+
logger.debug(f"Paused workflow state snapshot: {state_snapshot}")
|
|
936
|
+
|
|
937
|
+
# Return "success" with awaiting_user_input metadata
|
|
938
|
+
# The output contains the question details for the client
|
|
939
|
+
output = {
|
|
940
|
+
"question": e.question,
|
|
941
|
+
"input_type": e.input_type,
|
|
942
|
+
"options": e.options,
|
|
943
|
+
}
|
|
944
|
+
output_data = json.dumps(output).encode("utf-8")
|
|
945
|
+
|
|
946
|
+
return PyExecuteComponentResponse(
|
|
947
|
+
invocation_id=request.invocation_id,
|
|
948
|
+
success=True, # This is a valid pause state, not an error
|
|
949
|
+
output_data=output_data,
|
|
950
|
+
state_update=None,
|
|
951
|
+
error_message=None,
|
|
952
|
+
metadata=pause_metadata,
|
|
870
953
|
is_chunk=False,
|
|
871
954
|
done=True,
|
|
872
955
|
chunk_index=0,
|
agnt5/workflow.py
CHANGED
|
@@ -7,7 +7,7 @@ import functools
|
|
|
7
7
|
import inspect
|
|
8
8
|
import logging
|
|
9
9
|
import uuid
|
|
10
|
-
from typing import Any, Callable, Dict, Optional, TypeVar, cast
|
|
10
|
+
from typing import Any, Callable, Dict, List, Optional, TypeVar, cast
|
|
11
11
|
|
|
12
12
|
from ._schema_utils import extract_function_metadata, extract_function_schemas
|
|
13
13
|
from .context import Context
|
|
@@ -254,6 +254,86 @@ class WorkflowContext(Context):
|
|
|
254
254
|
|
|
255
255
|
return result
|
|
256
256
|
|
|
257
|
+
async def wait_for_user(
|
|
258
|
+
self,
|
|
259
|
+
question: str,
|
|
260
|
+
input_type: str = "text",
|
|
261
|
+
options: Optional[List[Dict]] = None
|
|
262
|
+
) -> str:
|
|
263
|
+
"""
|
|
264
|
+
Pause workflow execution and wait for user input.
|
|
265
|
+
|
|
266
|
+
On replay (even after worker crash), resumes from this point
|
|
267
|
+
with the user's response. This method enables human-in-the-loop
|
|
268
|
+
workflows by pausing execution and waiting for user interaction.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
question: Question to ask the user
|
|
272
|
+
input_type: Type of input - "text", "approval", or "choice"
|
|
273
|
+
options: For approval/choice, list of option dicts with 'id' and 'label'
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
User's response string
|
|
277
|
+
|
|
278
|
+
Raises:
|
|
279
|
+
WaitingForUserInputException: When no cached response exists (first call)
|
|
280
|
+
|
|
281
|
+
Example (text input):
|
|
282
|
+
```python
|
|
283
|
+
city = await ctx.wait_for_user("Which city?")
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Example (approval):
|
|
287
|
+
```python
|
|
288
|
+
decision = await ctx.wait_for_user(
|
|
289
|
+
"Approve this action?",
|
|
290
|
+
input_type="approval",
|
|
291
|
+
options=[
|
|
292
|
+
{"id": "approve", "label": "Approve"},
|
|
293
|
+
{"id": "reject", "label": "Reject"}
|
|
294
|
+
]
|
|
295
|
+
)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Example (choice):
|
|
299
|
+
```python
|
|
300
|
+
model = await ctx.wait_for_user(
|
|
301
|
+
"Which model?",
|
|
302
|
+
input_type="choice",
|
|
303
|
+
options=[
|
|
304
|
+
{"id": "gpt4", "label": "GPT-4"},
|
|
305
|
+
{"id": "claude", "label": "Claude"}
|
|
306
|
+
]
|
|
307
|
+
)
|
|
308
|
+
```
|
|
309
|
+
"""
|
|
310
|
+
from .exceptions import WaitingForUserInputException
|
|
311
|
+
|
|
312
|
+
# Generate unique step name for this user input request
|
|
313
|
+
# Using run_id ensures uniqueness across workflow execution
|
|
314
|
+
response_key = f"user_response:{self.run_id}"
|
|
315
|
+
|
|
316
|
+
# Check if we already have the user's response (replay scenario)
|
|
317
|
+
if self._workflow_entity.has_completed_step(response_key):
|
|
318
|
+
response = self._workflow_entity.get_completed_step(response_key)
|
|
319
|
+
self._logger.info("🔄 Replaying user response from checkpoint")
|
|
320
|
+
return response
|
|
321
|
+
|
|
322
|
+
# No response yet - pause execution
|
|
323
|
+
# Collect current workflow state for checkpoint
|
|
324
|
+
checkpoint_state = {}
|
|
325
|
+
if hasattr(self._workflow_entity, '_state') and self._workflow_entity._state is not None:
|
|
326
|
+
checkpoint_state = self._workflow_entity._state.get_state_snapshot()
|
|
327
|
+
|
|
328
|
+
self._logger.info(f"⏸️ Pausing workflow for user input: {question}")
|
|
329
|
+
|
|
330
|
+
raise WaitingForUserInputException(
|
|
331
|
+
question=question,
|
|
332
|
+
input_type=input_type,
|
|
333
|
+
options=options,
|
|
334
|
+
checkpoint_state=checkpoint_state
|
|
335
|
+
)
|
|
336
|
+
|
|
257
337
|
|
|
258
338
|
# ============================================================================
|
|
259
339
|
# WorkflowEntity: Entity specialized for workflow execution state
|
|
@@ -337,6 +417,26 @@ class WorkflowEntity(Entity):
|
|
|
337
417
|
"""Check if step has been completed."""
|
|
338
418
|
return step_name in self._completed_steps
|
|
339
419
|
|
|
420
|
+
def inject_user_response(self, response: str) -> None:
|
|
421
|
+
"""
|
|
422
|
+
Inject user response as a completed step for workflow resume.
|
|
423
|
+
|
|
424
|
+
This method is called by the worker when resuming a paused workflow
|
|
425
|
+
with the user's response. It stores the response as if it was a
|
|
426
|
+
completed step, allowing wait_for_user() to retrieve it on replay.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
response: User's response to inject
|
|
430
|
+
|
|
431
|
+
Example:
|
|
432
|
+
# Platform resumes workflow with user response
|
|
433
|
+
workflow_entity.inject_user_response("yes")
|
|
434
|
+
# On replay, wait_for_user() returns "yes" from cache
|
|
435
|
+
"""
|
|
436
|
+
response_key = f"user_response:{self.run_id}"
|
|
437
|
+
self._completed_steps[response_key] = response
|
|
438
|
+
logger.info(f"Injected user response for {self.run_id}: {response}")
|
|
439
|
+
|
|
340
440
|
@property
|
|
341
441
|
def state(self) -> "WorkflowState":
|
|
342
442
|
"""
|
|
@@ -408,6 +508,14 @@ class WorkflowState(EntityState):
|
|
|
408
508
|
"deleted": True
|
|
409
509
|
})
|
|
410
510
|
|
|
511
|
+
def has_changes(self) -> bool:
|
|
512
|
+
"""Check if any state changes have been tracked."""
|
|
513
|
+
return len(self._workflow_entity._state_changes) > 0
|
|
514
|
+
|
|
515
|
+
def get_state_snapshot(self) -> Dict[str, Any]:
|
|
516
|
+
"""Get current state as a snapshot dictionary."""
|
|
517
|
+
return dict(self._state)
|
|
518
|
+
|
|
411
519
|
|
|
412
520
|
class WorkflowRegistry:
|
|
413
521
|
"""Registry for workflow handlers."""
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
agnt5-0.2.
|
|
2
|
-
agnt5-0.2.
|
|
3
|
-
agnt5/__init__.py,sha256=
|
|
1
|
+
agnt5-0.2.8a5.dist-info/METADATA,sha256=0yneaLrJFbpRR65wwR3XBaUy-KsJDFoi191gMrC1558,996
|
|
2
|
+
agnt5-0.2.8a5.dist-info/WHEEL,sha256=ldAWRbzSJBX4WKLM9X0Sn1bQRKOUbOtmxzIIvOfIpnw,108
|
|
3
|
+
agnt5/__init__.py,sha256=liMb9egh56qvgY4Xvs9s7grOzF3lXSE8-nIksJLNAy4,2195
|
|
4
4
|
agnt5/_compat.py,sha256=BGuy3v5VDOHVa5f3Z-C22iMN19lAt0mPmXwF3qSSWxI,369
|
|
5
|
-
agnt5/_core.abi3.so,sha256=
|
|
5
|
+
agnt5/_core.abi3.so,sha256=zCSLHjigafC9boMNM1s23kRCit-rQOQ4As05ZyqHNk8,15286792
|
|
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=PuRq4cA8kJrBgrjRuDu62GBKJwefEjvndJZOj78_OGg,43293
|
|
10
10
|
agnt5/client.py,sha256=kXksazgxdVXWaG9OkjJA4cWruNtcS-ENhtnkrIdw-Nk,23212
|
|
11
11
|
agnt5/context.py,sha256=S2OzPkhn_jnqSWfT21mSYOux8vHaLKQxcAvggZDHQek,2378
|
|
12
12
|
agnt5/entity.py,sha256=AlHmSHVxQD5EYBvkmERKUkwv0ERrKaT8rvRK611hv_I,28941
|
|
13
|
-
agnt5/exceptions.py,sha256=
|
|
13
|
+
agnt5/exceptions.py,sha256=2YB7o6B0FBW2S7x47HnV-HaaEYVSsjRDAdZ9_MSD8Tw,2431
|
|
14
14
|
agnt5/function.py,sha256=f1vaAlJRwuo8cxCOGEd8XPido00mOhlPS8UJJx-6hJI,11041
|
|
15
15
|
agnt5/lm.py,sha256=9dFjd6eQ3f3lFZe7H7rWZherYiP_58MT1F5xpwD8PCg,23195
|
|
16
|
-
agnt5/tool.py,sha256=
|
|
16
|
+
agnt5/tool.py,sha256=dkShd97Y1cwSOUnTwvL2gr0CW-usRlaq4frki9kREXI,18008
|
|
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=
|
|
21
|
-
agnt5/workflow.py,sha256=
|
|
22
|
-
agnt5-0.2.
|
|
20
|
+
agnt5/worker.py,sha256=W5_yvy1vcflYjoPAGcIirbHXMbEXijXPJtGQOgl6oz4,51815
|
|
21
|
+
agnt5/workflow.py,sha256=cDxKK21mLrsgjLGmosinhKnAme30CoQqs_fUFvRsyI0,23456
|
|
22
|
+
agnt5-0.2.8a5.dist-info/RECORD,,
|
|
File without changes
|