Files
ProjectKoi-Kalo\Kalo 2c4cff8b63 强 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
2026-01-02 14:09:39 +08:00

106 lines
3.8 KiB
C#

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;
}
}
}