schema_cleaner.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. package antigravity
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. // CleanJSONSchema 清理 JSON Schema,移除 Antigravity/Gemini 不支持的字段
  7. // 参考 Antigravity-Manager/src-tauri/src/proxy/common/json_schema.rs 实现
  8. // 确保 schema 符合 JSON Schema draft 2020-12 且适配 Gemini v1internal
  9. func CleanJSONSchema(schema map[string]any) map[string]any {
  10. if schema == nil {
  11. return nil
  12. }
  13. // 0. 预处理:展开 $ref (Schema Flattening)
  14. // (Go map 是引用的,直接修改 schema)
  15. flattenRefs(schema, extractDefs(schema))
  16. // 递归清理
  17. cleaned := cleanJSONSchemaRecursive(schema)
  18. result, ok := cleaned.(map[string]any)
  19. if !ok {
  20. return nil
  21. }
  22. return result
  23. }
  24. // extractDefs 提取并移除定义的 helper
  25. func extractDefs(schema map[string]any) map[string]any {
  26. defs := make(map[string]any)
  27. if d, ok := schema["$defs"].(map[string]any); ok {
  28. for k, v := range d {
  29. defs[k] = v
  30. }
  31. delete(schema, "$defs")
  32. }
  33. if d, ok := schema["definitions"].(map[string]any); ok {
  34. for k, v := range d {
  35. defs[k] = v
  36. }
  37. delete(schema, "definitions")
  38. }
  39. return defs
  40. }
  41. // flattenRefs 递归展开 $ref
  42. func flattenRefs(schema map[string]any, defs map[string]any) {
  43. if len(defs) == 0 {
  44. return // 无需展开
  45. }
  46. // 检查并替换 $ref
  47. if ref, ok := schema["$ref"].(string); ok {
  48. delete(schema, "$ref")
  49. // 解析引用名 (例如 #/$defs/MyType -> MyType)
  50. parts := strings.Split(ref, "/")
  51. refName := parts[len(parts)-1]
  52. if defSchema, exists := defs[refName]; exists {
  53. if defMap, ok := defSchema.(map[string]any); ok {
  54. // 合并定义内容 (不覆盖现有 key)
  55. for k, v := range defMap {
  56. if _, has := schema[k]; !has {
  57. schema[k] = deepCopy(v) // 需深拷贝避免共享引用
  58. }
  59. }
  60. // 递归处理刚刚合并进来的内容
  61. flattenRefs(schema, defs)
  62. }
  63. }
  64. }
  65. // 遍历子节点
  66. for _, v := range schema {
  67. if subMap, ok := v.(map[string]any); ok {
  68. flattenRefs(subMap, defs)
  69. } else if subArr, ok := v.([]any); ok {
  70. for _, item := range subArr {
  71. if itemMap, ok := item.(map[string]any); ok {
  72. flattenRefs(itemMap, defs)
  73. }
  74. }
  75. }
  76. }
  77. }
  78. // deepCopy 深拷贝 (简单实现,仅针对 JSON 类型)
  79. func deepCopy(src any) any {
  80. if src == nil {
  81. return nil
  82. }
  83. switch v := src.(type) {
  84. case map[string]any:
  85. dst := make(map[string]any)
  86. for k, val := range v {
  87. dst[k] = deepCopy(val)
  88. }
  89. return dst
  90. case []any:
  91. dst := make([]any, len(v))
  92. for i, val := range v {
  93. dst[i] = deepCopy(val)
  94. }
  95. return dst
  96. default:
  97. return src
  98. }
  99. }
  100. // cleanJSONSchemaRecursive 递归核心清理逻辑
  101. // 返回处理后的值 (通常是 input map,但可能修改内部结构)
  102. func cleanJSONSchemaRecursive(value any) any {
  103. schemaMap, ok := value.(map[string]any)
  104. if !ok {
  105. return value
  106. }
  107. // 0. [NEW] 合并 allOf
  108. mergeAllOf(schemaMap)
  109. // 1. [CRITICAL] 深度递归处理子项
  110. if props, ok := schemaMap["properties"].(map[string]any); ok {
  111. for _, v := range props {
  112. cleanJSONSchemaRecursive(v)
  113. }
  114. // Go 中不需要像 Rust 那样显式处理 nullable_keys remove required,
  115. // 因为我们在子项处理中会正确设置 type 和 description
  116. } else if items, ok := schemaMap["items"]; ok {
  117. // [FIX] Gemini 期望 "items" 是单个 Schema 对象(列表验证),而不是数组(元组验证)。
  118. if itemsArr, ok := items.([]any); ok {
  119. // 策略:将元组 [A, B] 视为 A、B 中的最佳匹配项。
  120. best := extractBestSchemaFromUnion(itemsArr)
  121. if best == nil {
  122. // 回退到通用字符串
  123. best = map[string]any{"type": "string"}
  124. }
  125. // 用处理后的对象替换原有数组
  126. cleanedBest := cleanJSONSchemaRecursive(best)
  127. schemaMap["items"] = cleanedBest
  128. } else {
  129. cleanJSONSchemaRecursive(items)
  130. }
  131. } else {
  132. // 遍历所有值递归
  133. for _, v := range schemaMap {
  134. if _, isMap := v.(map[string]any); isMap {
  135. cleanJSONSchemaRecursive(v)
  136. } else if arr, isArr := v.([]any); isArr {
  137. for _, item := range arr {
  138. cleanJSONSchemaRecursive(item)
  139. }
  140. }
  141. }
  142. }
  143. // 2. [FIX] 处理 anyOf/oneOf 联合类型: 合并属性而非直接删除
  144. var unionArray []any
  145. typeStr, _ := schemaMap["type"].(string)
  146. if typeStr == "" || typeStr == "object" {
  147. if anyOf, ok := schemaMap["anyOf"].([]any); ok {
  148. unionArray = anyOf
  149. } else if oneOf, ok := schemaMap["oneOf"].([]any); ok {
  150. unionArray = oneOf
  151. }
  152. }
  153. if len(unionArray) > 0 {
  154. if bestBranch := extractBestSchemaFromUnion(unionArray); bestBranch != nil {
  155. if bestMap, ok := bestBranch.(map[string]any); ok {
  156. // 合并分支内容
  157. for k, v := range bestMap {
  158. if k == "properties" {
  159. targetProps, _ := schemaMap["properties"].(map[string]any)
  160. if targetProps == nil {
  161. targetProps = make(map[string]any)
  162. schemaMap["properties"] = targetProps
  163. }
  164. if sourceProps, ok := v.(map[string]any); ok {
  165. for pk, pv := range sourceProps {
  166. if _, exists := targetProps[pk]; !exists {
  167. targetProps[pk] = deepCopy(pv)
  168. }
  169. }
  170. }
  171. } else if k == "required" {
  172. targetReq, _ := schemaMap["required"].([]any)
  173. if sourceReq, ok := v.([]any); ok {
  174. for _, rv := range sourceReq {
  175. // 简单的去重添加
  176. exists := false
  177. for _, tr := range targetReq {
  178. if tr == rv {
  179. exists = true
  180. break
  181. }
  182. }
  183. if !exists {
  184. targetReq = append(targetReq, rv)
  185. }
  186. }
  187. schemaMap["required"] = targetReq
  188. }
  189. } else if _, exists := schemaMap[k]; !exists {
  190. schemaMap[k] = deepCopy(v)
  191. }
  192. }
  193. }
  194. }
  195. }
  196. // 3. [SAFETY] 检查当前对象是否为 JSON Schema 节点
  197. looksLikeSchema := hasKey(schemaMap, "type") ||
  198. hasKey(schemaMap, "properties") ||
  199. hasKey(schemaMap, "items") ||
  200. hasKey(schemaMap, "enum") ||
  201. hasKey(schemaMap, "anyOf") ||
  202. hasKey(schemaMap, "oneOf") ||
  203. hasKey(schemaMap, "allOf")
  204. if looksLikeSchema {
  205. // 4. [ROBUST] 约束迁移
  206. migrateConstraints(schemaMap)
  207. // 5. [CRITICAL] 白名单过滤
  208. allowedFields := map[string]bool{
  209. "type": true,
  210. "description": true,
  211. "properties": true,
  212. "required": true,
  213. "items": true,
  214. "enum": true,
  215. "title": true,
  216. }
  217. for k := range schemaMap {
  218. if !allowedFields[k] {
  219. delete(schemaMap, k)
  220. }
  221. }
  222. // 6. [SAFETY] 处理空 Object
  223. if t, _ := schemaMap["type"].(string); t == "object" {
  224. hasProps := false
  225. if props, ok := schemaMap["properties"].(map[string]any); ok && len(props) > 0 {
  226. hasProps = true
  227. }
  228. if !hasProps {
  229. schemaMap["properties"] = map[string]any{
  230. "reason": map[string]any{
  231. "type": "string",
  232. "description": "Reason for calling this tool",
  233. },
  234. }
  235. schemaMap["required"] = []any{"reason"}
  236. }
  237. }
  238. // 7. [SAFETY] Required 字段对齐
  239. if props, ok := schemaMap["properties"].(map[string]any); ok {
  240. if req, ok := schemaMap["required"].([]any); ok {
  241. var validReq []any
  242. for _, r := range req {
  243. if rStr, ok := r.(string); ok {
  244. if _, exists := props[rStr]; exists {
  245. validReq = append(validReq, r)
  246. }
  247. }
  248. }
  249. if len(validReq) > 0 {
  250. schemaMap["required"] = validReq
  251. } else {
  252. delete(schemaMap, "required")
  253. }
  254. }
  255. }
  256. // 8. 处理 type 字段 (Lowercase + Nullable 提取)
  257. isEffectivelyNullable := false
  258. if typeVal, exists := schemaMap["type"]; exists {
  259. var selectedType string
  260. switch v := typeVal.(type) {
  261. case string:
  262. lower := strings.ToLower(v)
  263. if lower == "null" {
  264. isEffectivelyNullable = true
  265. selectedType = "string" // fallback
  266. } else {
  267. selectedType = lower
  268. }
  269. case []any:
  270. // ["string", "null"]
  271. for _, t := range v {
  272. if ts, ok := t.(string); ok {
  273. lower := strings.ToLower(ts)
  274. if lower == "null" {
  275. isEffectivelyNullable = true
  276. } else if selectedType == "" {
  277. selectedType = lower
  278. }
  279. }
  280. }
  281. if selectedType == "" {
  282. selectedType = "string"
  283. }
  284. }
  285. schemaMap["type"] = selectedType
  286. } else {
  287. // 默认 object 如果有 properties (虽然上面白名单过滤可能删了 type 如果它不在... 但 type 必在 allowlist)
  288. // 如果没有 type,但有 properties,补一个
  289. if hasKey(schemaMap, "properties") {
  290. schemaMap["type"] = "object"
  291. } else {
  292. // 默认为 string ? or object? Gemini 通常需要明确 type
  293. schemaMap["type"] = "object"
  294. }
  295. }
  296. if isEffectivelyNullable {
  297. desc, _ := schemaMap["description"].(string)
  298. if !strings.Contains(desc, "nullable") {
  299. if desc != "" {
  300. desc += " "
  301. }
  302. desc += "(nullable)"
  303. schemaMap["description"] = desc
  304. }
  305. }
  306. // 9. Enum 值强制转字符串
  307. if enumVals, ok := schemaMap["enum"].([]any); ok {
  308. hasNonString := false
  309. for i, val := range enumVals {
  310. if _, isStr := val.(string); !isStr {
  311. hasNonString = true
  312. if val == nil {
  313. enumVals[i] = "null"
  314. } else {
  315. enumVals[i] = fmt.Sprintf("%v", val)
  316. }
  317. }
  318. }
  319. // If we mandated string values, we must ensure type is string
  320. if hasNonString {
  321. schemaMap["type"] = "string"
  322. }
  323. }
  324. }
  325. return schemaMap
  326. }
  327. func hasKey(m map[string]any, k string) bool {
  328. _, ok := m[k]
  329. return ok
  330. }
  331. func migrateConstraints(m map[string]any) {
  332. constraints := []struct {
  333. key string
  334. label string
  335. }{
  336. {"minLength", "minLen"},
  337. {"maxLength", "maxLen"},
  338. {"pattern", "pattern"},
  339. {"minimum", "min"},
  340. {"maximum", "max"},
  341. {"multipleOf", "multipleOf"},
  342. {"exclusiveMinimum", "exclMin"},
  343. {"exclusiveMaximum", "exclMax"},
  344. {"minItems", "minItems"},
  345. {"maxItems", "maxItems"},
  346. {"propertyNames", "propertyNames"},
  347. {"format", "format"},
  348. }
  349. var hints []string
  350. for _, c := range constraints {
  351. if val, ok := m[c.key]; ok && val != nil {
  352. hints = append(hints, fmt.Sprintf("%s: %v", c.label, val))
  353. }
  354. }
  355. if len(hints) > 0 {
  356. suffix := fmt.Sprintf(" [Constraint: %s]", strings.Join(hints, ", "))
  357. desc, _ := m["description"].(string)
  358. if !strings.Contains(desc, suffix) {
  359. m["description"] = desc + suffix
  360. }
  361. }
  362. }
  363. // mergeAllOf 合并 allOf
  364. func mergeAllOf(m map[string]any) {
  365. allOf, ok := m["allOf"].([]any)
  366. if !ok {
  367. return
  368. }
  369. delete(m, "allOf")
  370. mergedProps := make(map[string]any)
  371. mergedReq := make(map[string]bool)
  372. otherFields := make(map[string]any)
  373. for _, sub := range allOf {
  374. if subMap, ok := sub.(map[string]any); ok {
  375. // Props
  376. if props, ok := subMap["properties"].(map[string]any); ok {
  377. for k, v := range props {
  378. mergedProps[k] = v
  379. }
  380. }
  381. // Required
  382. if reqs, ok := subMap["required"].([]any); ok {
  383. for _, r := range reqs {
  384. if s, ok := r.(string); ok {
  385. mergedReq[s] = true
  386. }
  387. }
  388. }
  389. // Others
  390. for k, v := range subMap {
  391. if k != "properties" && k != "required" && k != "allOf" {
  392. if _, exists := otherFields[k]; !exists {
  393. otherFields[k] = v
  394. }
  395. }
  396. }
  397. }
  398. }
  399. // Apply
  400. for k, v := range otherFields {
  401. if _, exists := m[k]; !exists {
  402. m[k] = v
  403. }
  404. }
  405. if len(mergedProps) > 0 {
  406. existProps, _ := m["properties"].(map[string]any)
  407. if existProps == nil {
  408. existProps = make(map[string]any)
  409. m["properties"] = existProps
  410. }
  411. for k, v := range mergedProps {
  412. if _, exists := existProps[k]; !exists {
  413. existProps[k] = v
  414. }
  415. }
  416. }
  417. if len(mergedReq) > 0 {
  418. existReq, _ := m["required"].([]any)
  419. var validReqs []any
  420. for _, r := range existReq {
  421. if s, ok := r.(string); ok {
  422. validReqs = append(validReqs, s)
  423. delete(mergedReq, s) // already exists
  424. }
  425. }
  426. // append new
  427. for r := range mergedReq {
  428. validReqs = append(validReqs, r)
  429. }
  430. m["required"] = validReqs
  431. }
  432. }
  433. // extractBestSchemaFromUnion 从 anyOf/oneOf 中选取最佳分支
  434. func extractBestSchemaFromUnion(unionArray []any) any {
  435. var bestOption any
  436. bestScore := -1
  437. for _, item := range unionArray {
  438. score := scoreSchemaOption(item)
  439. if score > bestScore {
  440. bestScore = score
  441. bestOption = item
  442. }
  443. }
  444. return bestOption
  445. }
  446. func scoreSchemaOption(val any) int {
  447. m, ok := val.(map[string]any)
  448. if !ok {
  449. return 0
  450. }
  451. typeStr, _ := m["type"].(string)
  452. if hasKey(m, "properties") || typeStr == "object" {
  453. return 3
  454. }
  455. if hasKey(m, "items") || typeStr == "array" {
  456. return 2
  457. }
  458. if typeStr != "" && typeStr != "null" {
  459. return 1
  460. }
  461. return 0
  462. }
  463. // DeepCleanUndefined 深度清理值为 "[undefined]" 的字段
  464. func DeepCleanUndefined(value any) {
  465. if value == nil {
  466. return
  467. }
  468. switch v := value.(type) {
  469. case map[string]any:
  470. for k, val := range v {
  471. if s, ok := val.(string); ok && s == "[undefined]" {
  472. delete(v, k)
  473. continue
  474. }
  475. DeepCleanUndefined(val)
  476. }
  477. case []any:
  478. for _, val := range v {
  479. DeepCleanUndefined(val)
  480. }
  481. }
  482. }