Python: Fix HIL regression (#2167)

* fix devui regression from #2021 where all input is stringified but devui HIL input does not handle stringified json strings correctly.

* update incorrect test

* add devui hil input tests
This commit is contained in:
Victor Dibia
2025-11-12 21:26:24 -08:00
committed by GitHub
Unverified
parent e92dcb3d5d
commit 7e5de8f920
3 changed files with 49 additions and 10 deletions
@@ -781,6 +781,27 @@ class AgentFrameworkExecutor:
Returns:
Dict of {request_id: response_value} if found, None otherwise
"""
# Handle case where input_data might be a JSON string (from streamWorkflowExecutionOpenAI)
# The input field type is: str | list[Any] | dict[str, Any]
if isinstance(input_data, str):
try:
parsed = json.loads(input_data)
# Only use parsed value if it's a list (ResponseInputParam format expected for HIL)
if isinstance(parsed, list):
input_data = parsed
else:
# Parsed to dict, string, or primitive - not HIL response format
return None
except (json.JSONDecodeError, TypeError):
# Plain text string, not valid JSON - not HIL format
return None
# At this point, input_data should be a list or dict
# HIL responses are always in list format (ResponseInputParam)
if isinstance(input_data, dict):
# This is structured workflow input (dict), not HIL responses
return None
if not isinstance(input_data, list):
return None
@@ -261,6 +261,27 @@ def test_executor_parse_stringified_json_workflow_input():
assert parsed.metadata == {"key": "value"}
def test_extract_workflow_hil_responses_handles_stringified_json():
"""Test HIL response extraction handles both stringified and parsed JSON (regression test)."""
from agent_framework_devui._discovery import EntityDiscovery
from agent_framework_devui._executor import AgentFrameworkExecutor
from agent_framework_devui._mapper import MessageMapper
executor = AgentFrameworkExecutor(EntityDiscovery(None), MessageMapper())
# Regression test: Frontend sends stringified JSON via streamWorkflowExecutionOpenAI
stringified = '[{"type":"message","content":[{"type":"workflow_hil_response","responses":{"req_1":"spam"}}]}]'
assert executor._extract_workflow_hil_responses(stringified) == {"req_1": "spam"}
# Ensure parsed format still works
parsed = [{"type": "message", "content": [{"type": "workflow_hil_response", "responses": {"req_2": "ham"}}]}]
assert executor._extract_workflow_hil_responses(parsed) == {"req_2": "ham"}
# Non-HIL inputs should return None
assert executor._extract_workflow_hil_responses("plain text") is None
assert executor._extract_workflow_hil_responses({"email": "test"}) is None
async def test_executor_handles_non_streaming_agent():
"""Test executor can handle agents with only run() method (no run_stream)."""
from agent_framework import AgentRunResponse, AgentThread, ChatMessage, Role, TextContent
+7 -10
View File
@@ -280,18 +280,14 @@ async def test_api_restrictions_in_user_mode():
assert dev_client.get("/v1/entities").status_code == 200
assert user_client.get("/v1/entities").status_code == 200
# Test 4: Entity info should be restricted in user mode
# Test 4: Entity info should be accessible in both modes (UI needs this)
dev_response = dev_client.get("/v1/entities/test_agent/info")
assert dev_response.status_code in [200, 404, 500] # Not 403
user_response = user_client.get("/v1/entities/test_agent/info")
assert user_response.status_code == 403
error_data = user_response.json()
# FastAPI wraps HTTPException detail in 'detail' field
error = error_data.get("detail", {}).get("error") or error_data.get("error")
assert error is not None
assert "developer mode" in error["message"].lower()
assert error["code"] == "developer_mode_required"
# Should return 404 (entity doesn't exist) or 500 (other error), but NOT 403 (forbidden)
# User mode needs entity info to display workflows/agents in the UI
assert user_response.status_code in [200, 404, 500] # Not 403
# Test 5: Hot reload should be restricted in user mode
dev_response = dev_client.post("/v1/entities/test_agent/reload")
@@ -329,10 +325,11 @@ async def test_api_restrictions_in_user_mode():
# Test 8: Chat endpoint should work in both modes
chat_payload = {"model": "test_agent", "input": "Hello"}
dev_response = dev_client.post("/v1/responses", json=chat_payload)
assert dev_response.status_code in [200, 404] # 404 if agent doesn't exist
# 200=success, 400=missing entity_id in metadata, 404=entity not found
assert dev_response.status_code in [200, 400, 404]
user_response = user_client.post("/v1/responses", json=chat_payload)
assert user_response.status_code in [200, 404]
assert user_response.status_code in [200, 400, 404]
if __name__ == "__main__":