diff --git a/docs/gns3-copilot/ai-chat-api-design.md b/docs/gns3-copilot/ai-chat-api-design.md index b358fd732..28d36bcb0 100644 --- a/docs/gns3-copilot/ai-chat-api-design.md +++ b/docs/gns3-copilot/ai-chat-api-design.md @@ -247,6 +247,13 @@ Chat API uses Server-Sent Events (SSE) for streaming transmission. | tool_end | Tool execution complete | tool_name, tool_output, session_id | | error | Error message | error, session_id | | done | Stream end | session_id | + +**Tool Output Format** (`tool_output` field): +- If the tool returns a non-string type (dict, list), it is automatically serialized to JSON format using `json.dumps(obj, ensure_ascii=False, indent=2)` +- If the tool returns a string type, it is passed through as-is +- This ensures all structured data is in standard JSON format, making it easy for the frontend to parse with `JSON.parse()` +- Chinese and other non-ASCII characters are preserved (not escaped to `\uXXXX`) + | heartbeat | Heartbeat keepalive | session_id | ### Message Examples @@ -317,7 +324,7 @@ Chat API uses Server-Sent Events (SSE) for streaming transmission. { "type": "tool_end", "tool_name": "execute_multiple_device_commands", - "tool_output": "{...}", + "tool_output": "[\n {\n \"device_name\": \"R-1\",\n \"status\": \"success\",\n \"output\": \"Cisco IOS Software, \\n IOSv Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 15.2(1.90)\"\n },\n {\n \"device_name\": \"R-2\",\n \"status\": \"failed\",\n \"error\": \"Connection timeout\"\n }\n]", "session_id": "xxx" } diff --git a/gns3server/agent/gns3_copilot/agent_service.py b/gns3server/agent/gns3_copilot/agent_service.py index d42187477..4b03b4a4d 100644 --- a/gns3server/agent/gns3_copilot/agent_service.py +++ b/gns3server/agent/gns3_copilot/agent_service.py @@ -32,6 +32,7 @@ in the project directory. """ import asyncio +import json import logging import os from datetime import datetime @@ -460,9 +461,10 @@ class AgentService: elif event_type == "on_tool_end": # Tool execution completed output = data.get("output", "") - # Convert output to string if it's not already + # Convert output to JSON string if it's not already a string + # This ensures dict/list outputs are properly serialized for frontend parsing if not isinstance(output, str): - output = str(output) + output = json.dumps(output, ensure_ascii=False, indent=2) return { "type": "tool_end", "tool_name": event.get("name", ""),