This commit is contained in:
1 parent
df67a298be
commit
bb32884ecb
4 files changed
+69
-11
No files matched your search
@@ -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):
|
||||
|
||||
|
||||
@@ -1272,7 +1272,7 @@ def _execute_skill_tool(
|
||||
|
||||
|
||||
|
||||
return f"工具 '{func_name}' 执行结果: 已处理输入内容, 但未找到对应的技能定义。请基于通用知识回复用户。"
|
||||
return f"工具 '{func_name}' 执行结果: 已处理输入内容, 但未找到对应的技能定义。请基于通用知识回复用户。如有已加载的技能,可使用其名称直接调用。"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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; }
|
||||
@@ -531,18 +531,41 @@ const AiProcessBlock: React.FC<{ steps: any[]; autoExpand: boolean }> = ({ steps
|
||||
AI 思考 / 执行过程({steps.length} 步 — {open ? '自动收拢中... 点击手动收起' : '点击展开查看'})
|
||||
</summary>
|
||||
<div className="ai-process-list">
|
||||
{steps.map((s: any, i: number) => (
|
||||
<div key={i} className={`ai-process-item ai-process-${s.type}`}>
|
||||
<span className="ai-process-ico">
|
||||
{s.type === 'thinking' ? '💡' : s.type === 'skill' ? '🛠' : '🔌'}
|
||||
</span>
|
||||
{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 (
|
||||
<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">
|
||||
{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 === 'mcp' && (<><b className="ai-process-title">MCP · {s.server}:{s.name}</b><p className="ai-process-text">{(s.result || '').slice(0, 400)}</p></>)}
|
||||
{s.type === 'skill' && (<>
|
||||
<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>
|
||||
</details>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user