jaf-py 2.4.8__tar.gz → 2.5.1__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.
- {jaf_py-2.4.8/jaf_py.egg-info → jaf_py-2.5.1}/PKG-INFO +1 -1
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/engine.py +20 -19
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/tracing.py +12 -6
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/types.py +30 -2
- {jaf_py-2.4.8 → jaf_py-2.5.1/jaf_py.egg-info}/PKG-INFO +1 -1
- {jaf_py-2.4.8 → jaf_py-2.5.1}/pyproject.toml +1 -1
- {jaf_py-2.4.8 → jaf_py-2.5.1}/LICENSE +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/README.md +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/agent.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/agent_card.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/client.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/examples/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/examples/client_example.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/examples/integration_example.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/examples/rag_demo/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/examples/server_demo/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/examples/server_example.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/cleanup.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/factory.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/providers/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/providers/composite.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/providers/in_memory.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/providers/postgres.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/providers/redis.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/serialization.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/tests/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/tests/run_comprehensive_tests.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/tests/test_cleanup.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/tests/test_serialization.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/tests/test_stress_concurrency.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/tests/test_task_lifecycle.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/memory/types.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/protocol.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/server.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/standalone_client.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/tests/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/tests/run_tests.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/tests/test_agent.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/tests/test_client.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/tests/test_integration.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/tests/test_protocol.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/tests/test_types.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/a2a/types.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/cli.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/agent_tool.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/analytics.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/composition.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/errors.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/guardrails.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/parallel_agents.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/performance.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/proxy.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/proxy_helpers.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/state.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/streaming.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/tool_results.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/tools.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/core/workflows.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/exceptions.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/memory/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/memory/approval_storage.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/memory/factory.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/memory/providers/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/memory/providers/in_memory.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/memory/providers/postgres.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/memory/providers/redis.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/memory/types.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/memory/utils.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/plugins/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/plugins/base.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/policies/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/policies/handoff.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/policies/validation.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/providers/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/providers/mcp.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/providers/model.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/server/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/server/main.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/server/server.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/server/types.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/utils/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/utils/attachments.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/utils/document_processor.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/visualization/__init__.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/visualization/example.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/visualization/functional_core.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/visualization/graphviz.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/visualization/imperative_shell.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf/visualization/types.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf_py.egg-info/SOURCES.txt +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf_py.egg-info/dependency_links.txt +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf_py.egg-info/entry_points.txt +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf_py.egg-info/requires.txt +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/jaf_py.egg-info/top_level.txt +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/setup.cfg +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/setup.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_a2a_deep.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_a2a_examples.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_api_reference_examples.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_attachments.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_callback_system_examples.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_coffee_tool.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_conversation_id_fix.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_deployment_examples.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_docs_code_examples.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_engine.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_engine_manual.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_error_handling_examples.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_getting_started_examples.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_manual.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_math_tool.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_mcp_comprehensive.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_mcp_docs.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_mcp_real_functionality.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_mcp_transports.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_memory_system_examples.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_model_providers_examples.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_property_based.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_proxy_simple.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_redis_fixes.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_redis_memory.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_server_api_examples.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_session_continuity.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_streamable_http_mcp_example.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_timeout_functionality.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_tool_integration.py +0 -0
- {jaf_py-2.4.8 → jaf_py-2.5.1}/tests/test_validation.py +0 -0
|
@@ -279,8 +279,9 @@ async def _load_conversation_history(state: RunState[Ctx], config: RunConfig[Ctx
|
|
|
279
279
|
try:
|
|
280
280
|
content = json.loads(msg.content)
|
|
281
281
|
status = content.get('status')
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
hitl_status = content.get('hitl_status')
|
|
283
|
+
# Filter out ALL halted/pending approval messages (they're for audit only)
|
|
284
|
+
if status == 'halted' or hitl_status == 'pending_approval':
|
|
284
285
|
filtered_count += 1
|
|
285
286
|
continue # Skip this halted message
|
|
286
287
|
else:
|
|
@@ -765,7 +766,7 @@ async def _run_internal(
|
|
|
765
766
|
else:
|
|
766
767
|
try:
|
|
767
768
|
content = json.loads(msg.content)
|
|
768
|
-
if content.get('status') == 'halted':
|
|
769
|
+
if content.get('status') == 'halted' or content.get('hitl_status') == 'pending_approval':
|
|
769
770
|
# Remove this halted message if we have a new result for the same tool_call_id
|
|
770
771
|
if not any(result['message'].tool_call_id == msg.tool_call_id for result in tool_results):
|
|
771
772
|
cleaned_new_messages.append(msg)
|
|
@@ -793,7 +794,7 @@ async def _run_internal(
|
|
|
793
794
|
else:
|
|
794
795
|
try:
|
|
795
796
|
content = json.loads(msg.content)
|
|
796
|
-
if content.get('status') == 'halted':
|
|
797
|
+
if content.get('status') == 'halted' or content.get('hitl_status') == 'pending_approval':
|
|
797
798
|
# Remove this halted message if we have a new result for the same tool_call_id
|
|
798
799
|
if not any(result['message'].tool_call_id == msg.tool_call_id for result in tool_results):
|
|
799
800
|
cleaned_new_messages.append(msg)
|
|
@@ -1011,7 +1012,7 @@ async def _execute_tool_calls(
|
|
|
1011
1012
|
|
|
1012
1013
|
if not tool:
|
|
1013
1014
|
error_result = json.dumps({
|
|
1014
|
-
'
|
|
1015
|
+
'hitl_status': 'tool_not_found', # HITL workflow status
|
|
1015
1016
|
'message': f'Tool {tool_call.function.name} not found',
|
|
1016
1017
|
'tool_name': tool_call.function.name,
|
|
1017
1018
|
})
|
|
@@ -1022,7 +1023,7 @@ async def _execute_tool_calls(
|
|
|
1022
1023
|
result=error_result,
|
|
1023
1024
|
trace_id=state.trace_id,
|
|
1024
1025
|
run_id=state.run_id,
|
|
1025
|
-
|
|
1026
|
+
execution_status='error', # Tool execution failed
|
|
1026
1027
|
tool_result={'error': 'tool_not_found'},
|
|
1027
1028
|
call_id=tool_call.id
|
|
1028
1029
|
))))
|
|
@@ -1045,7 +1046,7 @@ async def _execute_tool_calls(
|
|
|
1045
1046
|
validated_args = raw_args
|
|
1046
1047
|
except ValidationError as e:
|
|
1047
1048
|
error_result = json.dumps({
|
|
1048
|
-
'
|
|
1049
|
+
'hitl_status': 'validation_error', # HITL workflow status
|
|
1049
1050
|
'message': f'Invalid arguments for {tool_call.function.name}: {e!s}',
|
|
1050
1051
|
'tool_name': tool_call.function.name,
|
|
1051
1052
|
'validation_errors': e.errors()
|
|
@@ -1057,7 +1058,7 @@ async def _execute_tool_calls(
|
|
|
1057
1058
|
result=error_result,
|
|
1058
1059
|
trace_id=state.trace_id,
|
|
1059
1060
|
run_id=state.run_id,
|
|
1060
|
-
|
|
1061
|
+
execution_status='error', # Tool execution failed due to validation
|
|
1061
1062
|
tool_result={'error': 'validation_error', 'details': e.errors()},
|
|
1062
1063
|
call_id=tool_call.id
|
|
1063
1064
|
))))
|
|
@@ -1114,7 +1115,7 @@ async def _execute_tool_calls(
|
|
|
1114
1115
|
|
|
1115
1116
|
# Return interrupted result with halted message
|
|
1116
1117
|
halted_result = json.dumps({
|
|
1117
|
-
'
|
|
1118
|
+
'hitl_status': 'pending_approval', # HITL workflow status: waiting for approval
|
|
1118
1119
|
'message': f'Tool {tool_call.function.name} requires approval.',
|
|
1119
1120
|
})
|
|
1120
1121
|
|
|
@@ -1131,7 +1132,7 @@ async def _execute_tool_calls(
|
|
|
1131
1132
|
if derived_status == 'rejected':
|
|
1132
1133
|
rejection_reason = approval_status.additional_context.get('rejection_reason', 'User declined the action') if approval_status.additional_context else 'User declined the action'
|
|
1133
1134
|
rejection_result = json.dumps({
|
|
1134
|
-
'
|
|
1135
|
+
'hitl_status': 'rejected', # HITL workflow status: user rejected the action
|
|
1135
1136
|
'message': f'Action was not approved. {rejection_reason}. Please ask if you can help with something else or suggest an alternative approach.',
|
|
1136
1137
|
'tool_name': tool_call.function.name,
|
|
1137
1138
|
'rejection_reason': rejection_reason,
|
|
@@ -1184,7 +1185,7 @@ async def _execute_tool_calls(
|
|
|
1184
1185
|
)
|
|
1185
1186
|
except asyncio.TimeoutError:
|
|
1186
1187
|
timeout_error_result = json.dumps({
|
|
1187
|
-
'
|
|
1188
|
+
'hitl_status': 'execution_timeout', # HITL workflow status
|
|
1188
1189
|
'message': f'Tool {tool_call.function.name} timed out after {timeout} seconds',
|
|
1189
1190
|
'tool_name': tool_call.function.name,
|
|
1190
1191
|
'timeout_seconds': timeout
|
|
@@ -1196,8 +1197,8 @@ async def _execute_tool_calls(
|
|
|
1196
1197
|
result=timeout_error_result,
|
|
1197
1198
|
trace_id=state.trace_id,
|
|
1198
1199
|
run_id=state.run_id,
|
|
1199
|
-
|
|
1200
|
-
tool_result={'error': '
|
|
1200
|
+
execution_status='timeout', # Tool execution timed out
|
|
1201
|
+
tool_result={'error': 'timeout'},
|
|
1201
1202
|
call_id=tool_call.id
|
|
1202
1203
|
))))
|
|
1203
1204
|
|
|
@@ -1222,7 +1223,7 @@ async def _execute_tool_calls(
|
|
|
1222
1223
|
# Wrap tool result with status information for approval context
|
|
1223
1224
|
if approval_status and approval_status.additional_context:
|
|
1224
1225
|
final_content = json.dumps({
|
|
1225
|
-
'
|
|
1226
|
+
'hitl_status': 'approved_and_executed', # HITL workflow status: approved by user and executed
|
|
1226
1227
|
'result': result_string,
|
|
1227
1228
|
'tool_name': tool_call.function.name,
|
|
1228
1229
|
'approval_context': approval_status.additional_context,
|
|
@@ -1230,14 +1231,14 @@ async def _execute_tool_calls(
|
|
|
1230
1231
|
})
|
|
1231
1232
|
elif needs_approval:
|
|
1232
1233
|
final_content = json.dumps({
|
|
1233
|
-
'
|
|
1234
|
+
'hitl_status': 'approved_and_executed', # HITL workflow status: approved by user and executed
|
|
1234
1235
|
'result': result_string,
|
|
1235
1236
|
'tool_name': tool_call.function.name,
|
|
1236
1237
|
'message': 'Tool was approved and executed successfully.'
|
|
1237
1238
|
})
|
|
1238
1239
|
else:
|
|
1239
1240
|
final_content = json.dumps({
|
|
1240
|
-
'
|
|
1241
|
+
'hitl_status': 'executed', # HITL workflow status: executed normally (no approval needed)
|
|
1241
1242
|
'result': result_string,
|
|
1242
1243
|
'tool_name': tool_call.function.name,
|
|
1243
1244
|
'message': 'Tool executed successfully.'
|
|
@@ -1250,7 +1251,7 @@ async def _execute_tool_calls(
|
|
|
1250
1251
|
trace_id=state.trace_id,
|
|
1251
1252
|
run_id=state.run_id,
|
|
1252
1253
|
tool_result=tool_result,
|
|
1253
|
-
|
|
1254
|
+
execution_status='success', # Tool execution succeeded
|
|
1254
1255
|
call_id=tool_call.id
|
|
1255
1256
|
))))
|
|
1256
1257
|
|
|
@@ -1277,7 +1278,7 @@ async def _execute_tool_calls(
|
|
|
1277
1278
|
|
|
1278
1279
|
except Exception as error:
|
|
1279
1280
|
error_result = json.dumps({
|
|
1280
|
-
'
|
|
1281
|
+
'hitl_status': 'execution_error', # HITL workflow status
|
|
1281
1282
|
'message': str(error),
|
|
1282
1283
|
'tool_name': tool_call.function.name,
|
|
1283
1284
|
})
|
|
@@ -1288,7 +1289,7 @@ async def _execute_tool_calls(
|
|
|
1288
1289
|
result=error_result,
|
|
1289
1290
|
trace_id=state.trace_id,
|
|
1290
1291
|
run_id=state.run_id,
|
|
1291
|
-
|
|
1292
|
+
execution_status='error', # Tool execution failed with exception
|
|
1292
1293
|
tool_result={'error': 'execution_error', 'detail': str(error)},
|
|
1293
1294
|
call_id=tool_call.id
|
|
1294
1295
|
))))
|
|
@@ -341,7 +341,7 @@ class LangfuseTraceCollector:
|
|
|
341
341
|
public_key=public_key,
|
|
342
342
|
secret_key=secret_key,
|
|
343
343
|
host=host,
|
|
344
|
-
release="jaf-py-v2.
|
|
344
|
+
release="jaf-py-v2.5.1"
|
|
345
345
|
)
|
|
346
346
|
self.active_spans: Dict[str, Any] = {}
|
|
347
347
|
self.trace_spans: Dict[TraceId, Any] = {}
|
|
@@ -509,19 +509,23 @@ class LangfuseTraceCollector:
|
|
|
509
509
|
"user_id": user_id or event.data.get("user_id")
|
|
510
510
|
}
|
|
511
511
|
}
|
|
512
|
-
|
|
512
|
+
|
|
513
|
+
# Extract agent_name for tagging
|
|
514
|
+
agent_name = event.data.get("agent_name") or "analytics_agent_jaf"
|
|
515
|
+
|
|
513
516
|
trace = self.langfuse.trace(
|
|
514
|
-
name=
|
|
517
|
+
name=agent_name,
|
|
515
518
|
user_id=user_id or event.data.get("user_id"),
|
|
516
519
|
session_id=event.data.get("session_id"),
|
|
517
520
|
input=trace_input,
|
|
521
|
+
tags=[agent_name], # Add agent_name as a tag for dashboard filtering
|
|
518
522
|
metadata={
|
|
519
523
|
"framework": "jaf",
|
|
520
524
|
"event_type": "run_start",
|
|
521
525
|
"trace_id": str(trace_id),
|
|
522
526
|
"user_query": user_query,
|
|
523
527
|
"user_id": user_id or event.data.get("user_id"),
|
|
524
|
-
"agent_name":
|
|
528
|
+
"agent_name": agent_name,
|
|
525
529
|
"conversation_history": conversation_history,
|
|
526
530
|
"tool_calls": [],
|
|
527
531
|
"tool_results": [],
|
|
@@ -716,7 +720,8 @@ class LangfuseTraceCollector:
|
|
|
716
720
|
"result": tool_result,
|
|
717
721
|
"call_id": call_id,
|
|
718
722
|
"timestamp": datetime.now().isoformat(),
|
|
719
|
-
"
|
|
723
|
+
"execution_status": event.data.get("execution_status", "completed"),
|
|
724
|
+
"status": event.data.get("execution_status", "completed"), # DEPRECATED: backward compatibility
|
|
720
725
|
"tool_result": event.data.get("tool_result")
|
|
721
726
|
}
|
|
722
727
|
|
|
@@ -731,7 +736,8 @@ class LangfuseTraceCollector:
|
|
|
731
736
|
"result": tool_result,
|
|
732
737
|
"call_id": call_id,
|
|
733
738
|
"timestamp": datetime.now().isoformat(),
|
|
734
|
-
"
|
|
739
|
+
"execution_status": event.data.get("execution_status", "completed"),
|
|
740
|
+
"status": event.data.get("execution_status", "completed") # DEPRECATED: backward compatibility
|
|
735
741
|
}
|
|
736
742
|
|
|
737
743
|
# End the span with detailed output
|
|
@@ -619,15 +619,43 @@ class ToolCallStartEvent:
|
|
|
619
619
|
|
|
620
620
|
@dataclass(frozen=True)
|
|
621
621
|
class ToolCallEndEventData:
|
|
622
|
-
"""
|
|
622
|
+
"""
|
|
623
|
+
Data for tool call end events.
|
|
624
|
+
|
|
625
|
+
IMPORTANT: There are two different status concepts:
|
|
626
|
+
1. execution_status (this field): Indicates whether the tool execution itself succeeded or failed
|
|
627
|
+
- 'success': Tool executed without errors
|
|
628
|
+
- 'error': Tool execution failed due to validation, not found, or runtime errors
|
|
629
|
+
- 'timeout': Tool execution timed out
|
|
630
|
+
|
|
631
|
+
2. hitl_status (in result JSON): Indicates HITL workflow status
|
|
632
|
+
- 'executed': Tool ran normally (no approval needed)
|
|
633
|
+
- 'approved_and_executed': Tool required approval, was approved, and executed
|
|
634
|
+
- 'pending_approval': Tool requires approval and is waiting
|
|
635
|
+
- 'rejected': Tool was rejected by user
|
|
636
|
+
- 'execution_error', 'validation_error', etc.: Various error states
|
|
637
|
+
"""
|
|
623
638
|
tool_name: str
|
|
624
639
|
result: str
|
|
625
640
|
trace_id: TraceId
|
|
626
641
|
run_id: RunId
|
|
627
642
|
tool_result: Optional[Any] = None
|
|
628
|
-
|
|
643
|
+
execution_status: Optional[str] = None # success/error/timeout - indicates if tool executed successfully
|
|
644
|
+
status: Optional[str] = None # DEPRECATED: maintained for backward-compatible initialization/serialization
|
|
629
645
|
call_id: Optional[str] = None
|
|
630
646
|
|
|
647
|
+
def __post_init__(self) -> None:
|
|
648
|
+
# Handle backward compatibility with explicit conflict detection
|
|
649
|
+
if self.execution_status is not None and self.status is not None and self.execution_status != self.status:
|
|
650
|
+
raise ValueError(
|
|
651
|
+
f"Conflicting values for execution_status ('{self.execution_status}') and status ('{self.status}'). "
|
|
652
|
+
f"Please use only execution_status for new code."
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
# Prefer execution_status (new field) over status (deprecated field)
|
|
656
|
+
canonical = self.execution_status if self.execution_status is not None else self.status
|
|
657
|
+
object.__setattr__(self, 'execution_status', canonical)
|
|
658
|
+
object.__setattr__(self, 'status', canonical)
|
|
631
659
|
@dataclass(frozen=True)
|
|
632
660
|
class ToolCallEndEvent:
|
|
633
661
|
type: Literal['tool_call_end'] = 'tool_call_end'
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "jaf-py"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.5.1"
|
|
8
8
|
description = "A purely functional agent framework with immutable state and composable tools - Python implementation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|