From bb32884ecb33e041b44f0d71bc5066d2d9edcb6b Mon Sep 17 00:00:00 2001 From: QihuAI Design Date: Sun, 26 Jul 2026 20:59:52 +0800 Subject: [PATCH] p2-mcp-schema-error-step-enhancements --- backend/python/app/api/chat_service.py | 34 ++++++++++++++++++++-- backend/python/app/api/chat_skill.py | 2 +- frontend/src/pages/front/ChatPage.css | 5 ++++ frontend/src/pages/front/ChatPage.tsx | 39 ++++++++++++++++++++------ 4 files changed, 69 insertions(+), 11 deletions(-) diff --git a/backend/python/app/api/chat_service.py b/backend/python/app/api/chat_service.py index d9cad49..e31602a 100644 --- a/backend/python/app/api/chat_service.py +++ b/backend/python/app/api/chat_service.py @@ -3463,6 +3463,7 @@ async def _send_message_core( _schema = {"type": "object", "properties": {}} + _schema = _normalize_mcp_schema(_schema) @@ -5228,7 +5229,7 @@ async def _send_message_core( - tool_result = f"[工具调用已被策略拦截] {_policy_check.get('message', _reason)}" + tool_result = f"[策略拦截: {_policy_check.get('message', _reason)}] 建议: 检查权限设置或新开对话" @@ -5404,7 +5405,7 @@ async def _send_message_core( - tool_result = "[MCP 工具未找到或映射缺失]" + tool_result = "[MCP 工具未找到] 建议: 检查工具名或尝试其他MCP工具。可用: " + str(list(mcp_tool_map.keys())[:5]) @@ -8402,6 +8403,35 @@ def _resolve_agent_tool_policy(agents): +def _normalize_mcp_schema(schema: dict) -> dict: + """MCP JSON Schema -> OpenAI function calling 兼容。 + 处理: $ref, oneOf/anyOf -> first option, 剥离非标关键字""" + if not isinstance(schema, dict): + return {"type": "object", "properties": {}} + if "$ref" in schema: + return {"type": "object", "properties": {}} + if "oneOf" in schema or "anyOf" in schema: + opts = schema.get("oneOf") or schema.get("anyOf") or [] + if opts and isinstance(opts[0], dict): + return _normalize_mcp_schema(opts[0]) + return {"type": "object", "properties": {}} + if "allOf" in schema: + merged = {"type": "object", "properties": {}} + for s in schema.get("allOf", []): + sub = _normalize_mcp_schema(s) + if sub.get("properties"): + merged["properties"].update(sub["properties"]) + return merged + result = {} + for k in ("type", "properties", "required", "description", "enum", "items"): + if k in schema: + result[k] = schema[k] + if result.get("type") != "object": + result["type"] = "object" + if "properties" not in result: + result["properties"] = {} + return result + def _check_tool_policy(func_name, mcp_info, skills, policy, call_count, round_count): diff --git a/backend/python/app/api/chat_skill.py b/backend/python/app/api/chat_skill.py index 20f7e1f..453f62c 100644 --- a/backend/python/app/api/chat_skill.py +++ b/backend/python/app/api/chat_skill.py @@ -1272,7 +1272,7 @@ def _execute_skill_tool( - return f"工具 '{func_name}' 执行结果: 已处理输入内容, 但未找到对应的技能定义。请基于通用知识回复用户。" + return f"工具 '{func_name}' 执行结果: 已处理输入内容, 但未找到对应的技能定义。请基于通用知识回复用户。如有已加载的技能,可使用其名称直接调用。" diff --git a/frontend/src/pages/front/ChatPage.css b/frontend/src/pages/front/ChatPage.css index 781698d..ad6bd9e 100644 --- a/frontend/src/pages/front/ChatPage.css +++ b/frontend/src/pages/front/ChatPage.css @@ -1779,3 +1779,8 @@ html.dark .ai-sending-timeout { width: 16px; height: 16px; } + +.ai-process-running { animation: aiPulse 1.5s ease-in-out infinite; } +@keyframes aiPulse { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } } +.ai-process-error { background: rgba(255,0,0,0.05); border-left: 2px solid #e74c3c; } +.ai-process-running { border-left: 2px solid #3498db; } diff --git a/frontend/src/pages/front/ChatPage.tsx b/frontend/src/pages/front/ChatPage.tsx index 61bd85b..0569705 100644 --- a/frontend/src/pages/front/ChatPage.tsx +++ b/frontend/src/pages/front/ChatPage.tsx @@ -531,18 +531,41 @@ const AiProcessBlock: React.FC<{ steps: any[]; autoExpand: boolean }> = ({ steps AI 思考 / 执行过程({steps.length} 步 — {open ? '自动收拢中... 点击手动收起' : '点击展开查看'})
- {steps.map((s: any, i: number) => ( -
- - {s.type === 'thinking' ? '💡' : s.type === 'skill' ? '🛠' : '🔌'} - + {steps.map((s: any, i: number) => { + const isLast = i === steps.length - 1; + const hasResult = s.result && s.result.length > 0; + const isError = hasResult && (s.result.includes('error') || s.result.includes('失败') || s.result.includes('异常') || s.result.includes('拦截')); + const isRunning = isLast && !hasResult; + const stepIcon = s.type === 'thinking' ? '💡' : s.type === 'skill' ? '🛠' : '🔌'; + const statusIcon = isRunning ? ' ⏳' : isError ? ' ❌' : hasResult ? ' ✅' : ''; + const resultDisplay = (s.result || '').length > 400 ? (s.result || '').slice(0, 400) + '...' : (s.result || ''); + + return ( +
+ {stepIcon}{statusIcon}
{s.type === 'thinking' && (<>思考

{s.text}

)} - {s.type === 'skill' && (<>调用技能:{s.name}

{(s.result || '').slice(0, 400)}

)} - {s.type === 'mcp' && (<>MCP · {s.server}:{s.name}

{(s.result || '').slice(0, 400)}

)} + {s.type === 'skill' && (<> + 调用技能:{s.name}{statusIcon} + {s.args && Object.keys(s.args).length > 0 && ( + + 参数: {JSON.stringify(s.args).slice(0, 200)} + + )} + {hasResult &&

{resultDisplay}

} + )} + {s.type === 'mcp' && (<> + MCP · {s.server}:{s.name}{statusIcon} + {s.args && Object.keys(s.args).length > 0 && ( + + 参数: {JSON.stringify(s.args).slice(0, 200)} + + )} + {hasResult &&

{resultDisplay}

} + )}
- ))} + )})}
);