p2-mcp-schema-error-step-enhancements
CI / gate (push) Has been cancelled

This commit is contained in:
QihuAI Design committed 2026-07-26 20:59:52 +08:00
1 parent df67a298be
commit bb32884ecb
4 files changed
+69 -11

No files matched your search

+32 -2
View File
@@ -3463,6 +3463,7 @@ async def _send_message_core(
_schema = {"type": "object", "properties": {}} _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): def _check_tool_policy(func_name, mcp_info, skills, policy, call_count, round_count):
+1 -1
View File
@@ -1272,7 +1272,7 @@ def _execute_skill_tool(
return f"工具 '{func_name}' 执行结果: 已处理输入内容, 但未找到对应的技能定义。请基于通用知识回复用户。" return f"工具 '{func_name}' 执行结果: 已处理输入内容, 但未找到对应的技能定义。请基于通用知识回复用户。如有已加载的技能,可使用其名称直接调用。"
+5
View File
@@ -1779,3 +1779,8 @@ html.dark .ai-sending-timeout {
width: 16px; width: 16px;
height: 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; }
+31 -8
View File
@@ -531,18 +531,41 @@ const AiProcessBlock: React.FC<{ steps: any[]; autoExpand: boolean }> = ({ steps
AI 思考 / 执行过程({steps.length} 步 — {open ? '自动收拢中... 点击手动收起' : '点击展开查看'}) AI 思考 / 执行过程({steps.length} 步 — {open ? '自动收拢中... 点击手动收起' : '点击展开查看'})
</summary> </summary>
<div className="ai-process-list"> <div className="ai-process-list">
{steps.map((s: any, i: number) => ( {steps.map((s: any, i: number) => {
<div key={i} className={`ai-process-item ai-process-${s.type}`}> const isLast = i === steps.length - 1;
<span className="ai-process-ico"> const hasResult = s.result && s.result.length > 0;
{s.type === 'thinking' ? '💡' : s.type === 'skill' ? '🛠' : '🔌'} const isError = hasResult && (s.result.includes('error') || s.result.includes('失败') || s.result.includes('异常') || s.result.includes('拦截'));
</span> 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 (
<div key={i} className={`ai-process-item ai-process-${s.type}${isRunning ? ' ai-process-running' : ''}${isError ? ' ai-process-error' : ''}`}>
<span className="ai-process-ico">{stepIcon}{statusIcon}</span>
<div className="ai-process-body"> <div className="ai-process-body">
{s.type === 'thinking' && (<><b className="ai-process-title">思考</b><p className="ai-process-text">{s.text}</p></>)} {s.type === 'thinking' && (<><b className="ai-process-title">思考</b><p className="ai-process-text">{s.text}</p></>)}
{s.type === 'skill' && (<><b className="ai-process-title">调用技能:{s.name}</b><p className="ai-process-text">{(s.result || '').slice(0, 400)}</p></>)} {s.type === 'skill' && (<>
{s.type === 'mcp' && (<><b className="ai-process-title">MCP · {s.server}:{s.name}</b><p className="ai-process-text">{(s.result || '').slice(0, 400)}</p></>)} <b className="ai-process-title">调用技能:{s.name}{statusIcon}</b>
{s.args && Object.keys(s.args).length > 0 && (
<span style={{fontSize:'12px',color:'#666',margin:'2px 0',display:'block'}}>
参数: {JSON.stringify(s.args).slice(0, 200)}
</span>
)}
{hasResult && <p className="ai-process-text">{resultDisplay}</p>}
</>)}
{s.type === 'mcp' && (<>
<b className="ai-process-title">MCP · {s.server}:{s.name}{statusIcon}</b>
{s.args && Object.keys(s.args).length > 0 && (
<span style={{fontSize:'12px',color:'#666',margin:'2px 0',display:'block'}}>
参数: {JSON.stringify(s.args).slice(0, 200)}
</span>
)}
{hasResult && <p className="ai-process-text">{resultDisplay}</p>}
</>)}
</div> </div>
</div> </div>
))} )})}
</div> </div>
</details> </details>
); );