强 schema 清洗:所有工具定义在生成时统一清洗,补齐 properties、规整 type、强制 additionalProperties=false,避免 schema 漏洞导致模型随意输出。AITool.cs、ToolSchemaSanitizer.cs
预执行拦截(Claude Code 风格):工具执行前校验 JSON、必填字段、类型、未知字段;失败则返回错误 ToolResult 反馈给模型,允许自修正或降级回复。ToolCallValidator.cs、AIIntelligenceCore.cs 重试清洗:retry_tools 解析前剥离 json 围栏,避免误判。AIIntelligenceCore.cs 工具选择:Query 阶段强制 tool_choice: "required",Action 维持 auto。AIIntelligenceCore.cs
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WulaFallenEmpire.EventSystem.AI.Utils
|
||||
{
|
||||
public static class ToolSchemaSanitizer
|
||||
{
|
||||
public static Dictionary<string, object> Sanitize(Dictionary<string, object> schema)
|
||||
{
|
||||
if (schema == null) return new Dictionary<string, object>();
|
||||
|
||||
string schemaType = NormalizeType(schema);
|
||||
if (string.IsNullOrWhiteSpace(schemaType))
|
||||
{
|
||||
schemaType = "object";
|
||||
schema["type"] = schemaType;
|
||||
}
|
||||
|
||||
if (string.Equals(schemaType, "object", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!TryGetDict(schema, "properties", out var props))
|
||||
{
|
||||
props = new Dictionary<string, object>();
|
||||
schema["properties"] = props;
|
||||
}
|
||||
|
||||
var sanitizedProps = new Dictionary<string, object>();
|
||||
foreach (var entry in props)
|
||||
{
|
||||
if (entry.Value is Dictionary<string, object> child)
|
||||
{
|
||||
sanitizedProps[entry.Key] = Sanitize(child);
|
||||
}
|
||||
else
|
||||
{
|
||||
sanitizedProps[entry.Key] = new Dictionary<string, object>
|
||||
{
|
||||
["type"] = "string"
|
||||
};
|
||||
}
|
||||
}
|
||||
schema["properties"] = sanitizedProps;
|
||||
|
||||
if (!schema.ContainsKey("additionalProperties"))
|
||||
{
|
||||
schema["additionalProperties"] = false;
|
||||
}
|
||||
|
||||
if (schema.TryGetValue("required", out object requiredRaw) && requiredRaw is List<object> requiredList)
|
||||
{
|
||||
var filtered = new List<object>();
|
||||
foreach (var item in requiredList)
|
||||
{
|
||||
string name = item as string;
|
||||
if (string.IsNullOrWhiteSpace(name)) continue;
|
||||
if (sanitizedProps.ContainsKey(name))
|
||||
{
|
||||
filtered.Add(name);
|
||||
}
|
||||
}
|
||||
schema["required"] = filtered;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(schemaType, "array", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (schema.TryGetValue("items", out object itemsObj) && itemsObj is Dictionary<string, object> itemSchema)
|
||||
{
|
||||
schema["items"] = Sanitize(itemSchema);
|
||||
}
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
private static string NormalizeType(Dictionary<string, object> schema)
|
||||
{
|
||||
if (!schema.TryGetValue("type", out object typeObj) || typeObj == null) return null;
|
||||
if (typeObj is string s) return s;
|
||||
if (typeObj is List<object> list)
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (item is string candidate && !string.Equals(candidate, "null", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
schema["type"] = candidate;
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool TryGetDict(Dictionary<string, object> root, string key, out Dictionary<string, object> value)
|
||||
{
|
||||
value = null;
|
||||
if (root == null || string.IsNullOrWhiteSpace(key)) return false;
|
||||
if (root.TryGetValue(key, out object raw) && raw is Dictionary<string, object> dict)
|
||||
{
|
||||
value = dict;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user