Skip to content

Commit 7e5de8f

Browse files
authored
Python: Fix HIL regression (microsoft#2167)
* fix devui regression from microsoft#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
1 parent e92dcb3 commit 7e5de8f

3 files changed

Lines changed: 49 additions & 10 deletions

File tree

python/packages/devui/agent_framework_devui/_executor.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,27 @@ def _extract_workflow_hil_responses(self, input_data: Any) -> dict[str, Any] | N
781781
Returns:
782782
Dict of {request_id: response_value} if found, None otherwise
783783
"""
784+
# Handle case where input_data might be a JSON string (from streamWorkflowExecutionOpenAI)
785+
# The input field type is: str | list[Any] | dict[str, Any]
786+
if isinstance(input_data, str):
787+
try:
788+
parsed = json.loads(input_data)
789+
# Only use parsed value if it's a list (ResponseInputParam format expected for HIL)
790+
if isinstance(parsed, list):
791+
input_data = parsed
792+
else:
793+
# Parsed to dict, string, or primitive - not HIL response format
794+
return None
795+
except (json.JSONDecodeError, TypeError):
796+
# Plain text string, not valid JSON - not HIL format
797+
return None
798+
799+
# At this point, input_data should be a list or dict
800+
# HIL responses are always in list format (ResponseInputParam)
801+
if isinstance(input_data, dict):
802+
# This is structured workflow input (dict), not HIL responses
803+
return None
804+
784805
if not isinstance(input_data, list):
785806
return None
786807

python/packages/devui/tests/test_execution.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,27 @@ class WorkflowInput(BaseModel):
261261
assert parsed.metadata == {"key": "value"}
262262

263263

264+
def test_extract_workflow_hil_responses_handles_stringified_json():
265+
"""Test HIL response extraction handles both stringified and parsed JSON (regression test)."""
266+
from agent_framework_devui._discovery import EntityDiscovery
267+
from agent_framework_devui._executor import AgentFrameworkExecutor
268+
from agent_framework_devui._mapper import MessageMapper
269+
270+
executor = AgentFrameworkExecutor(EntityDiscovery(None), MessageMapper())
271+
272+
# Regression test: Frontend sends stringified JSON via streamWorkflowExecutionOpenAI
273+
stringified = '[{"type":"message","content":[{"type":"workflow_hil_response","responses":{"req_1":"spam"}}]}]'
274+
assert executor._extract_workflow_hil_responses(stringified) == {"req_1": "spam"}
275+
276+
# Ensure parsed format still works
277+
parsed = [{"type": "message", "content": [{"type": "workflow_hil_response", "responses": {"req_2": "ham"}}]}]
278+
assert executor._extract_workflow_hil_responses(parsed) == {"req_2": "ham"}
279+
280+
# Non-HIL inputs should return None
281+
assert executor._extract_workflow_hil_responses("plain text") is None
282+
assert executor._extract_workflow_hil_responses({"email": "test"}) is None
283+
284+
264285
async def test_executor_handles_non_streaming_agent():
265286
"""Test executor can handle agents with only run() method (no run_stream)."""
266287
from agent_framework import AgentRunResponse, AgentThread, ChatMessage, Role, TextContent

python/packages/devui/tests/test_server.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -280,18 +280,14 @@ async def test_api_restrictions_in_user_mode():
280280
assert dev_client.get("/v1/entities").status_code == 200
281281
assert user_client.get("/v1/entities").status_code == 200
282282

283-
# Test 4: Entity info should be restricted in user mode
283+
# Test 4: Entity info should be accessible in both modes (UI needs this)
284284
dev_response = dev_client.get("/v1/entities/test_agent/info")
285285
assert dev_response.status_code in [200, 404, 500] # Not 403
286286

287287
user_response = user_client.get("/v1/entities/test_agent/info")
288-
assert user_response.status_code == 403
289-
error_data = user_response.json()
290-
# FastAPI wraps HTTPException detail in 'detail' field
291-
error = error_data.get("detail", {}).get("error") or error_data.get("error")
292-
assert error is not None
293-
assert "developer mode" in error["message"].lower()
294-
assert error["code"] == "developer_mode_required"
288+
# Should return 404 (entity doesn't exist) or 500 (other error), but NOT 403 (forbidden)
289+
# User mode needs entity info to display workflows/agents in the UI
290+
assert user_response.status_code in [200, 404, 500] # Not 403
295291

296292
# Test 5: Hot reload should be restricted in user mode
297293
dev_response = dev_client.post("/v1/entities/test_agent/reload")
@@ -329,10 +325,11 @@ async def test_api_restrictions_in_user_mode():
329325
# Test 8: Chat endpoint should work in both modes
330326
chat_payload = {"model": "test_agent", "input": "Hello"}
331327
dev_response = dev_client.post("/v1/responses", json=chat_payload)
332-
assert dev_response.status_code in [200, 404] # 404 if agent doesn't exist
328+
# 200=success, 400=missing entity_id in metadata, 404=entity not found
329+
assert dev_response.status_code in [200, 400, 404]
333330

334331
user_response = user_client.post("/v1/responses", json=chat_payload)
335-
assert user_response.status_code in [200, 404]
332+
assert user_response.status_code in [200, 400, 404]
336333

337334

338335
if __name__ == "__main__":

0 commit comments

Comments
 (0)