Skip to content

Conversation

@fengxsong
Copy link
Contributor

Description

添加_try_loads辅助函数处理JSON解析,改进工具调用和结果输出的健壮性
当参数或输出不是有效JSON时,使用默认值或原始字符串

当使用web-ui进行测试时,当在之前的对话中调用了plugin_call时,在这里会失败。

测试的payload

{"input": [{"sequence_number": null, "object": "message", "status": "created", "error": null, "id": "msg_380c1bb2-24bb-45e0-9147-01b15141bb1a", "type": "message", "role": "user", "content": [{"sequence_number": null, "object": "content", "status": "created", "error": null, "type": "text", "index": null, "delta": false, "msg_id": null, "text": "你好啊"}], "code": null, "message": null, "usage": null, "metadata": null}, {"sequence_number": 9, "object": "message", "status": "completed", "error": null, "id": "msg_aaa88a75-4e00-408d-8389-df734708f6c9", "type": "message", "role": "assistant", "content": [{"sequence_number": 8, "object": "content", "status": "completed", "error": null, "type": "text", "index": null, "delta": null, "msg_id": null, "text": "你好!请问有什么我可以帮你的吗?"}], "code": null, "message": null, "usage": null, "metadata": null}, {"sequence_number": null, "object": "message", "status": "created", "error": null, "id": "msg_d2c22af4-2a6f-466f-b987-446e23d40e19", "type": "message", "role": "user", "content": [{"sequence_number": null, "object": "content", "status": "created", "error": null, "type": "text", "index": null, "delta": false, "msg_id": null, "text": "暂时没啥事"}], "code": null, "message": null, "usage": null, "metadata": null}, {"sequence_number": 10, "object": "message", "status": "completed", "error": null, "id": "msg_d73fac37-52ab-449a-9064-f676133cc34a", "type": "message", "role": "assistant", "content": [{"sequence_number": 9, "object": "content", "status": "completed", "error": null, "type": "text", "index": null, "delta": null, "msg_id": null, "text": "收到,当前无任务待处理。如有需要,请随时告知!"}], "code": null, "message": null, "usage": null, "metadata": null}, {"sequence_number": null, "object": "message", "status": "created", "error": null, "id": "msg_9d608921-0eb9-47a1-90e7-fb0f5f3ac293", "type": "message", "role": "user", "content": [{"sequence_number": null, "object": "content", "status": "created", "error": null, "type": "text", "index": null, "delta": false, "msg_id": null, "text": "看看devops namespace有什么服务"}], "code": null, "message": null, "usage": null, "metadata": null}, {"sequence_number": 5, "object": "message", "status": "completed", "error": null, "id": "msg_69f41d40-1d9e-4751-bf21-ae46080c2ade", "type": "plugin_call", "role": "assistant", "content": [{"sequence_number": null, "object": "content", "status": null, "error": null, "type": "data", "index": null, "delta": null, "msg_id": null, "data": {"call_id": "call_6035dadd0c3a4dadbbe1551a", "name": "handoff_observability", "arguments": ""}}, {"sequence_number": null, "object": "content", "status": null, "error": null, "type": "data", "index": null, "delta": null, "msg_id": null, "data": {"call_id": "call_6035dadd0c3a4dadbbe1551a", "name": "handoff_observability", "arguments": "{\"task\": \"\\u5217\\u51fa devops namespace \\u4e2d\\u7684\\u6240\\u6709\\u670d\\u52a1\"}"}}], "code": null, "message": null, "usage": null, "metadata": null}, {"sequence_number": 9, "object": "message", "status": "completed", "error": null, "id": "msg_05943276-e34d-4501-a238-8f0e77fe24ed", "type": "plugin_call", "role": "assistant", "content": [{"sequence_number": null, "object": "content", "status": null, "error": null, "type": "data", "index": null, "delta": null, "msg_id": null, "data": {"call_id": "call_e501a6af09164ab78624bb32", "name": "kubectl_get", "arguments": ""}}, {"sequence_number": null, "object": "content", "status": null, "error": null, "type": "data", "index": null, "delta": null, "msg_id": null, "data": {"call_id": "call_e501a6af09164ab78624bb32", "name": "kubectl_get", "arguments": "{\"resourceType\": \"services\", \"namespace\": \"devops\", \"name\": \"\"}"}}], "code": null, "message": null, "usage": null, "metadata": null}, {"sequence_number": 10, "object": "message", "status": "completed", "error": null, "id": "msg_3461cba0-dd30-44f2-b63b-a5fd981c2bd7", "type": "plugin_call_output", "role": "tool", "content": [{"sequence_number": null, "object": "content", "status": null, "error": null, "type": "data", "index": null, "delta": false, "msg_id": null, "data": {"call_id": "call_e501a6af09164ab78624bb32", "name": "kubectl_get", "output": "[{\"type\": \"text\", \"text\": \"{\\n  \\\"items\\\": [\\n    {\\n      \\\"name\\\": \\\"blackbox-prometheus-blackbox-exporter\\\",\\n      \\\"namespace\\\": \\\"devops\\\",\\n      \\\"kind\\\": \\\"Service\\\",\\n      \\\"status\\\": \\\"ClusterIP\\\",\\n      \\\"createdAt\\\": \\\"2025-04-09T07:31:39Z\\\"\\n    },\\n    {\\n      \\\"name\\\": \\\"devops-ai-code-review\\\",\\n      \\\"namespace\\\": \\\"devops\\\",\\n      \\\"kind\\\": \\\"Service\\\",\\n      \\\"status\\\": \\\"ClusterIP\\\",\\n      \\\"createdAt\\\": \\\"2024-10-17T07:10:11Z\\\"\\n    },\\n    {\\n      \\\"name\\\": \\\"devops-assistant-service\\\",\\n      \\\"namespace\\\": \\\"devops\\\",\\n      \\\"kind\\\": \\\"Service\\\",\\n      \\\"status\\\": \\\"ClusterIP\\\",\\n      \\\"createdAt\\\": \\\"2024-10-31T09:29:12Z\\\"\\n    },\\n    {\\n      \\\"name\\\": \\\"devops-cicd-ui-service\\\",\\n      \\\"namespace\\\": \\\"devops\\\",\\n      \\\"kind\\\": \\\"Service\\\",\\n      \\\"status\\\": \\\"ClusterIP\\\",\\n      \\\"createdAt\\\": \\\"2025-04-08T02:42:57Z\\\"\\n    },\\n    {\\n      \\\"name\\\": \\\"devops-iam-service\\\",\\n      \\\"namespace\\\": \\\"devops\\\",\\n      \\\"kind\\\": \\\"Service\\\",\\n      \\\"status\\\": \\\"ClusterIP\\\",\\n      \\\"createdAt\\\": \\\"2025-04-16T07:17:58Z\\\"\\n    },\\n    {\\n      \\\"name\\\": \\\"devops-tunnel-service\\\",\\n      \\\"namespace\\\": \\\"devops\\\",\\n      \\\"kind\\\": \\\"Service\\\",\\n      \\\"status\\\": \\\"ClusterIP\\\",\\n      \\\"createdAt\\\": \\\"2025-04-08T02:23:55Z\\\"\\n    },\\n    {\\n      \\\"name\\\": \\\"tunnel-service\\\",\\n      \\\"namespace\\\": \\\"devops\\\",\\n      \\\"kind\\\": \\\"Service\\\",\\n      \\\"status\\\": \\\"ClusterIP\\\",\\n      \\\"createdAt\\\": \\\"2025-03-23T03:38:51Z\\\"\\n    }\\n  ]\\n}\"}]"}}], "code": null, "message": null, "usage": null, "metadata": null}, {"sequence_number": 89, "object": "message", "status": "completed", "error": null, "id": "msg_0d84e9f0-319d-4a38-8237-a7e6c387036b", "type": "message", "role": "assistant", "content": [{"sequence_number": 88, "object": "content", "status": "completed", "error": null, "type": "text", "index": null, "delta": null, "msg_id": null, "text": "在 `devops` 命名空间中,当前存在以下服务(共 7 个):\n\n| 服务名称                              | 类型       | 创建时间                |\n|-------------------------------------|------------|------------------------|\n| blackbox-prometheus-blackbox-exporter | ClusterIP  | 2025-04-09T07:31:39Z   |\n| devops-ai-code-review                 | ClusterIP  | 2024-10-17T07:10:11Z   |\n| devops-assistant-service              | ClusterIP  | 2024-10-31T09:29:12Z   |\n| devops-cicd-ui-service                | ClusterIP  | 2025-04-08T02:42:57Z   |\n| devops-iam-service                    | ClusterIP  | 2025-04-16T07:17:58Z   |\n| devops-tunnel-service                 | ClusterIP  | 2025-04-08T02:23:55Z   |\n| tunnel-service                        | ClusterIP  | 2025-03-23T03:38:51Z   |\n\n所有服务均为 `ClusterIP` 类型,仅在集群内部可访问。如需进一步信息(如端口、Selector 或关联的 Pod),可指定具体服务名称进行详细查询。"}], "code": null, "message": null, "usage": null, "metadata": null}, {"sequence_number": 90, "object": "message", "status": "completed", "error": null, "id": "msg_01095b61-9168-44bc-b5f8-c97a8ed8ff89", "type": "plugin_call_output", "role": "tool", "content": [{"sequence_number": null, "object": "content", "status": null, "error": null, "type": "data", "index": null, "delta": false, "msg_id": null, "data": {"call_id": "call_6035dadd0c3a4dadbbe1551a", "name": "handoff_observability", "output": "[{\"type\": \"text\", \"text\": \"在 `devops` 命名空间中,当前存在以下服务(共 7 个):\\n\\n| 服务名称                              | 类型       | 创建时间                |\\n|-------------------------------------|------------|------------------------|\\n| blackbox-prometheus-blackbox-exporter | ClusterIP  | 2025-04-09T07:31:39Z   |\\n| devops-ai-code-review                 | ClusterIP  | 2024-10-17T07:10:11Z   |\\n| devops-assistant-service              | ClusterIP  | 2024-10-31T09:29:12Z   |\\n| devops-cicd-ui-service                | ClusterIP  | 2025-04-08T02:42:57Z   |\\n| devops-iam-service                    | ClusterIP  | 2025-04-16T07:17:58Z   |\\n| devops-tunnel-service                 | ClusterIP  | 2025-04-08T02:23:55Z   |\\n| tunnel-service                        | ClusterIP  | 2025-03-23T03:38:51Z   |\\n\\n所有服务均为 `ClusterIP` 类型,仅在集群内部可访问。如需进一步信息(如端口、Selector 或关联的 Pod),可指定具体服务名称进行详细查询。\"}]"}}], "code": null, "message": null, "usage": null, "metadata": null}, {"sequence_number": 183, "object": "message", "status": "completed", "error": null, "id": "msg_24230bed-5b28-4aed-881e-7b37b6f0a922", "type": "message", "role": "assistant", "content": [{"sequence_number": 182, "object": "content", "status": "completed", "error": null, "type": "text", "index": null, "delta": null, "msg_id": null, "text": "### 1) 观察与关键线索  \n- `devops` 命名空间中共有 **7 个服务**,均为 `ClusterIP` 类型,仅限集群内部访问。  \n- 服务包括:监控(blackbox-exporter)、AI 代码审查、IAM、CICD UI、协作门户、隧道服务等,覆盖 DevOps 全链路功能。  \n- 最近创建的服务是 `devops-mcp-resource`(2025-09-09),可能与新上线模块相关。\n\n### 2) 假设与优先级  \n- 用户意图可能是:  \n  a) **例行巡检**:确认服务清单是否符合预期;  \n  b) **故障排查**:某功能异常需定位对应服务;  \n  c) **安全审计**:检查是否存在冗余或未授权服务(如重复的 `tunnel-service`)。  \n- **优先级**:若无进一步指示,默认按 **巡检** 处理,无需深入分析。\n\n### 3) 验证计划与所需工具  \n- 当前信息已完整列出服务,无需额外工具调用。  \n- 若用户后续指定某服务异常,则需通过 `handoff_observability` 查询其 Pod/日志,或通过 `handoff_diagnosis` 分析依赖关系。\n\n### 4) 行动方案与回滚  \n- **行动**:输出服务清单供用户核对(已完成)。  \n- **回滚**:无操作执行,无需回滚。\n\n### 5) 确认项与成功判据  \n- 用户确认清单是否符合预期,或提出进一步需求(如检查某服务状态)。\n\n### 6) 风险等级与影响范围  \n- **风险等级**:低(仅读取元数据,无变更操作)。  \n- **影响范围**:无。  \n\n---  \n请确认是否需要针对某个具体服务深入检查(如状态、日志或配置)?"}], "code": null, "message": null, "usage": null, "metadata": null}, {"sequence_number": null, "object": "message", "status": "created", "error": null, "id": "msg_751982ff-6d75-4ff5-a1e1-7ce201a929d9", "type": "message", "role": "user", "content": [{"sequence_number": null, "object": "content", "status": "created", "error": null, "type": "text", "index": null, "delta": false, "msg_id": null, "text": "看看 devops-iam-service 有没什么性能问题"}], "code": null, "message": null, "usage": null, "metadata": null}], "stream": true, "id": null, "model": null, "top_p": null, "temperature": null, "frequency_penalty": null, "presence_penalty": null, "max_tokens": null, "stop": null, "n": 1, "seed": null, "tools": null, "session_id": "1764915547757", "user_id": "1764915547757"}

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Refactoring

Component(s) Affected

  • Engine
  • Sandbox
  • Tools
  • Common
  • Documentation
  • Tests
  • CI/CD

Checklist

  • Pre-commit hooks pass
  • Tests pass locally
  • Documentation updated (if needed)
  • Ready for review

Testing

Additional Notes

添加_try_loads辅助函数处理JSON解析,改进工具调用和结果输出的健壮性
当参数或输出不是有效JSON时,使用默认值或原始字符串
@rayrayraykk rayrayraykk requested a review from Copilot December 5, 2025 07:18
Copilot finished reviewing on behalf of rayrayraykk December 5, 2025 07:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a bug in the message conversion logic when handling tool calls and their outputs. The issue occurred when the web-ui processed conversations containing plugin_call messages, causing failures during message parsing. The fix adds robust JSON parsing with fallback handling for cases where arguments or outputs are not valid JSON strings.

Key Changes:

  • Added _try_loads helper function to safely parse JSON with configurable fallback behavior
  • Modified tool call argument extraction to handle non-JSON and empty string cases
  • Modified tool output extraction to preserve original strings when JSON parsing fails
Comments suppressed due to low confidence (1)

src/agentscope_runtime/adapters/agentscope/message.py:438

  • Similar to the tool arguments extraction, the output extraction logic finds the output value from content items in reverse order, but then uses message.content[0].data["call_id"] and message.content[0].data.get("name") for the ToolResultBlock creation at lines 434-435.

This creates the same inconsistency: if the valid output is found in a different content item, the call_id and name might not match. Consider extracting all fields from the same content item:

output_data = None
for cnt in reversed(message.content):
    if hasattr(cnt, "data") and "call_id" in cnt.data:
        v = cnt.data.get("output")
        if isinstance(v, (dict, list)) or (isinstance(v, str) and v.strip()):
            output_data = {
                "call_id": cnt.data["call_id"],
                "name": cnt.data.get("name"),
                "output": _try_loads(v, "", keep_original=True)
            }
            break

if output_data is None:
    output_data = {
        "call_id": message.content[0].data.get("call_id", ""),
        "name": message.content[0].data.get("name"),
        "output": ""
    }

blk = output_data["output"]
# ... rest of validation logic ...

result["content"] = [
    ToolResultBlock(
        type="tool_result",
        id=output_data["call_id"],
        name=output_data["name"],
        output=blk,
    ),
]
            out = None
            for cnt in reversed(message.content):
                if hasattr(cnt, "data"):
                    v = cnt.data.get("output")
                    if isinstance(v, (dict, list)) or (
                        isinstance(v, str) and v.strip()
                    ):
                        out = _try_loads(v, "", keep_original=True)
                        break
            if out is None:
                out = ""
            blk = out

            def is_valid_block(obj):
                return any(
                    matches_typed_dict_structure(obj, cls)
                    for cls in (TextBlock, ImageBlock, AudioBlock, VideoBlock)
                )

            if isinstance(blk, list):
                if not all(is_valid_block(item) for item in blk):
                    blk = out
            elif isinstance(blk, dict):
                if not is_valid_block(blk):
                    blk = out
            else:
                blk = out

            result["content"] = [
                ToolResultBlock(
                    type="tool_result",
                    id=message.content[0].data["call_id"],
                    name=message.content[0].data.get("name"),
                    output=blk,
                ),
            ]

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Member

@rayrayraykk rayrayraykk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contributions, it looks good to me.

@rayrayraykk rayrayraykk merged commit 9106495 into agentscope-ai:main Dec 5, 2025
13 checks passed
@rayrayraykk
Copy link
Member

@all-contributors please add @fengxsong for bug

@allcontributors
Copy link
Contributor

@rayrayraykk

I've put up a pull request to add @fengxsong! 🎉

@fengxsong fengxsong deleted the fix_convert_message branch December 5, 2025 07:57
rayrayraykk added a commit that referenced this pull request Dec 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants