override_test.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975
  1. package common
  2. import (
  3. "encoding/json"
  4. "reflect"
  5. "testing"
  6. "github.com/QuantumNous/new-api/types"
  7. )
  8. func TestApplyParamOverrideTrimPrefix(t *testing.T) {
  9. // trim_prefix example:
  10. // {"operations":[{"path":"model","mode":"trim_prefix","value":"openai/"}]}
  11. input := []byte(`{"model":"openai/gpt-4","temperature":0.7}`)
  12. override := map[string]interface{}{
  13. "operations": []interface{}{
  14. map[string]interface{}{
  15. "path": "model",
  16. "mode": "trim_prefix",
  17. "value": "openai/",
  18. },
  19. },
  20. }
  21. out, err := ApplyParamOverride(input, override, nil)
  22. if err != nil {
  23. t.Fatalf("ApplyParamOverride returned error: %v", err)
  24. }
  25. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  26. }
  27. func TestApplyParamOverrideTrimSuffix(t *testing.T) {
  28. // trim_suffix example:
  29. // {"operations":[{"path":"model","mode":"trim_suffix","value":"-latest"}]}
  30. input := []byte(`{"model":"gpt-4-latest","temperature":0.7}`)
  31. override := map[string]interface{}{
  32. "operations": []interface{}{
  33. map[string]interface{}{
  34. "path": "model",
  35. "mode": "trim_suffix",
  36. "value": "-latest",
  37. },
  38. },
  39. }
  40. out, err := ApplyParamOverride(input, override, nil)
  41. if err != nil {
  42. t.Fatalf("ApplyParamOverride returned error: %v", err)
  43. }
  44. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  45. }
  46. func TestApplyParamOverrideTrimNoop(t *testing.T) {
  47. // trim_prefix no-op example:
  48. // {"operations":[{"path":"model","mode":"trim_prefix","value":"openai/"}]}
  49. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  50. override := map[string]interface{}{
  51. "operations": []interface{}{
  52. map[string]interface{}{
  53. "path": "model",
  54. "mode": "trim_prefix",
  55. "value": "openai/",
  56. },
  57. },
  58. }
  59. out, err := ApplyParamOverride(input, override, nil)
  60. if err != nil {
  61. t.Fatalf("ApplyParamOverride returned error: %v", err)
  62. }
  63. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  64. }
  65. func TestApplyParamOverrideTrimRequiresValue(t *testing.T) {
  66. // trim_prefix requires value example:
  67. // {"operations":[{"path":"model","mode":"trim_prefix"}]}
  68. input := []byte(`{"model":"gpt-4"}`)
  69. override := map[string]interface{}{
  70. "operations": []interface{}{
  71. map[string]interface{}{
  72. "path": "model",
  73. "mode": "trim_prefix",
  74. },
  75. },
  76. }
  77. _, err := ApplyParamOverride(input, override, nil)
  78. if err == nil {
  79. t.Fatalf("expected error, got nil")
  80. }
  81. }
  82. func TestApplyParamOverrideReplace(t *testing.T) {
  83. // replace example:
  84. // {"operations":[{"path":"model","mode":"replace","from":"openai/","to":""}]}
  85. input := []byte(`{"model":"openai/gpt-4o-mini","temperature":0.7}`)
  86. override := map[string]interface{}{
  87. "operations": []interface{}{
  88. map[string]interface{}{
  89. "path": "model",
  90. "mode": "replace",
  91. "from": "openai/",
  92. "to": "",
  93. },
  94. },
  95. }
  96. out, err := ApplyParamOverride(input, override, nil)
  97. if err != nil {
  98. t.Fatalf("ApplyParamOverride returned error: %v", err)
  99. }
  100. assertJSONEqual(t, `{"model":"gpt-4o-mini","temperature":0.7}`, string(out))
  101. }
  102. func TestApplyParamOverrideRegexReplace(t *testing.T) {
  103. // regex_replace example:
  104. // {"operations":[{"path":"model","mode":"regex_replace","from":"^gpt-","to":"openai/gpt-"}]}
  105. input := []byte(`{"model":"gpt-4o-mini","temperature":0.7}`)
  106. override := map[string]interface{}{
  107. "operations": []interface{}{
  108. map[string]interface{}{
  109. "path": "model",
  110. "mode": "regex_replace",
  111. "from": "^gpt-",
  112. "to": "openai/gpt-",
  113. },
  114. },
  115. }
  116. out, err := ApplyParamOverride(input, override, nil)
  117. if err != nil {
  118. t.Fatalf("ApplyParamOverride returned error: %v", err)
  119. }
  120. assertJSONEqual(t, `{"model":"openai/gpt-4o-mini","temperature":0.7}`, string(out))
  121. }
  122. func TestApplyParamOverrideReplaceRequiresFrom(t *testing.T) {
  123. // replace requires from example:
  124. // {"operations":[{"path":"model","mode":"replace"}]}
  125. input := []byte(`{"model":"gpt-4"}`)
  126. override := map[string]interface{}{
  127. "operations": []interface{}{
  128. map[string]interface{}{
  129. "path": "model",
  130. "mode": "replace",
  131. },
  132. },
  133. }
  134. _, err := ApplyParamOverride(input, override, nil)
  135. if err == nil {
  136. t.Fatalf("expected error, got nil")
  137. }
  138. }
  139. func TestApplyParamOverrideRegexReplaceRequiresPattern(t *testing.T) {
  140. // regex_replace requires from(pattern) example:
  141. // {"operations":[{"path":"model","mode":"regex_replace"}]}
  142. input := []byte(`{"model":"gpt-4"}`)
  143. override := map[string]interface{}{
  144. "operations": []interface{}{
  145. map[string]interface{}{
  146. "path": "model",
  147. "mode": "regex_replace",
  148. },
  149. },
  150. }
  151. _, err := ApplyParamOverride(input, override, nil)
  152. if err == nil {
  153. t.Fatalf("expected error, got nil")
  154. }
  155. }
  156. func TestApplyParamOverrideDelete(t *testing.T) {
  157. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  158. override := map[string]interface{}{
  159. "operations": []interface{}{
  160. map[string]interface{}{
  161. "path": "temperature",
  162. "mode": "delete",
  163. },
  164. },
  165. }
  166. out, err := ApplyParamOverride(input, override, nil)
  167. if err != nil {
  168. t.Fatalf("ApplyParamOverride returned error: %v", err)
  169. }
  170. var got map[string]interface{}
  171. if err := json.Unmarshal(out, &got); err != nil {
  172. t.Fatalf("failed to unmarshal output JSON: %v", err)
  173. }
  174. if _, exists := got["temperature"]; exists {
  175. t.Fatalf("expected temperature to be deleted")
  176. }
  177. }
  178. func TestApplyParamOverrideSet(t *testing.T) {
  179. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  180. override := map[string]interface{}{
  181. "operations": []interface{}{
  182. map[string]interface{}{
  183. "path": "temperature",
  184. "mode": "set",
  185. "value": 0.1,
  186. },
  187. },
  188. }
  189. out, err := ApplyParamOverride(input, override, nil)
  190. if err != nil {
  191. t.Fatalf("ApplyParamOverride returned error: %v", err)
  192. }
  193. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(out))
  194. }
  195. func TestApplyParamOverrideSetKeepOrigin(t *testing.T) {
  196. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  197. override := map[string]interface{}{
  198. "operations": []interface{}{
  199. map[string]interface{}{
  200. "path": "temperature",
  201. "mode": "set",
  202. "value": 0.1,
  203. "keep_origin": true,
  204. },
  205. },
  206. }
  207. out, err := ApplyParamOverride(input, override, nil)
  208. if err != nil {
  209. t.Fatalf("ApplyParamOverride returned error: %v", err)
  210. }
  211. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  212. }
  213. func TestApplyParamOverrideMove(t *testing.T) {
  214. input := []byte(`{"model":"gpt-4","meta":{"x":1}}`)
  215. override := map[string]interface{}{
  216. "operations": []interface{}{
  217. map[string]interface{}{
  218. "mode": "move",
  219. "from": "model",
  220. "to": "meta.model",
  221. },
  222. },
  223. }
  224. out, err := ApplyParamOverride(input, override, nil)
  225. if err != nil {
  226. t.Fatalf("ApplyParamOverride returned error: %v", err)
  227. }
  228. assertJSONEqual(t, `{"meta":{"x":1,"model":"gpt-4"}}`, string(out))
  229. }
  230. func TestApplyParamOverrideMoveMissingSource(t *testing.T) {
  231. input := []byte(`{"meta":{"x":1}}`)
  232. override := map[string]interface{}{
  233. "operations": []interface{}{
  234. map[string]interface{}{
  235. "mode": "move",
  236. "from": "model",
  237. "to": "meta.model",
  238. },
  239. },
  240. }
  241. _, err := ApplyParamOverride(input, override, nil)
  242. if err == nil {
  243. t.Fatalf("expected error, got nil")
  244. }
  245. }
  246. func TestApplyParamOverridePrependAppendString(t *testing.T) {
  247. input := []byte(`{"model":"gpt-4"}`)
  248. override := map[string]interface{}{
  249. "operations": []interface{}{
  250. map[string]interface{}{
  251. "path": "model",
  252. "mode": "prepend",
  253. "value": "openai/",
  254. },
  255. map[string]interface{}{
  256. "path": "model",
  257. "mode": "append",
  258. "value": "-latest",
  259. },
  260. },
  261. }
  262. out, err := ApplyParamOverride(input, override, nil)
  263. if err != nil {
  264. t.Fatalf("ApplyParamOverride returned error: %v", err)
  265. }
  266. assertJSONEqual(t, `{"model":"openai/gpt-4-latest"}`, string(out))
  267. }
  268. func TestApplyParamOverridePrependAppendArray(t *testing.T) {
  269. input := []byte(`{"arr":[1,2]}`)
  270. override := map[string]interface{}{
  271. "operations": []interface{}{
  272. map[string]interface{}{
  273. "path": "arr",
  274. "mode": "prepend",
  275. "value": 0,
  276. },
  277. map[string]interface{}{
  278. "path": "arr",
  279. "mode": "append",
  280. "value": []interface{}{3, 4},
  281. },
  282. },
  283. }
  284. out, err := ApplyParamOverride(input, override, nil)
  285. if err != nil {
  286. t.Fatalf("ApplyParamOverride returned error: %v", err)
  287. }
  288. assertJSONEqual(t, `{"arr":[0,1,2,3,4]}`, string(out))
  289. }
  290. func TestApplyParamOverrideAppendObjectMergeKeepOrigin(t *testing.T) {
  291. input := []byte(`{"obj":{"a":1}}`)
  292. override := map[string]interface{}{
  293. "operations": []interface{}{
  294. map[string]interface{}{
  295. "path": "obj",
  296. "mode": "append",
  297. "keep_origin": true,
  298. "value": map[string]interface{}{
  299. "a": 2,
  300. "b": 3,
  301. },
  302. },
  303. },
  304. }
  305. out, err := ApplyParamOverride(input, override, nil)
  306. if err != nil {
  307. t.Fatalf("ApplyParamOverride returned error: %v", err)
  308. }
  309. assertJSONEqual(t, `{"obj":{"a":1,"b":3}}`, string(out))
  310. }
  311. func TestApplyParamOverrideAppendObjectMergeOverride(t *testing.T) {
  312. input := []byte(`{"obj":{"a":1}}`)
  313. override := map[string]interface{}{
  314. "operations": []interface{}{
  315. map[string]interface{}{
  316. "path": "obj",
  317. "mode": "append",
  318. "value": map[string]interface{}{
  319. "a": 2,
  320. "b": 3,
  321. },
  322. },
  323. },
  324. }
  325. out, err := ApplyParamOverride(input, override, nil)
  326. if err != nil {
  327. t.Fatalf("ApplyParamOverride returned error: %v", err)
  328. }
  329. assertJSONEqual(t, `{"obj":{"a":2,"b":3}}`, string(out))
  330. }
  331. func TestApplyParamOverrideConditionORDefault(t *testing.T) {
  332. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  333. override := map[string]interface{}{
  334. "operations": []interface{}{
  335. map[string]interface{}{
  336. "path": "temperature",
  337. "mode": "set",
  338. "value": 0.1,
  339. "conditions": []interface{}{
  340. map[string]interface{}{
  341. "path": "model",
  342. "mode": "prefix",
  343. "value": "gpt",
  344. },
  345. map[string]interface{}{
  346. "path": "model",
  347. "mode": "prefix",
  348. "value": "claude",
  349. },
  350. },
  351. },
  352. },
  353. }
  354. out, err := ApplyParamOverride(input, override, nil)
  355. if err != nil {
  356. t.Fatalf("ApplyParamOverride returned error: %v", err)
  357. }
  358. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(out))
  359. }
  360. func TestApplyParamOverrideConditionAND(t *testing.T) {
  361. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  362. override := map[string]interface{}{
  363. "operations": []interface{}{
  364. map[string]interface{}{
  365. "path": "temperature",
  366. "mode": "set",
  367. "value": 0.1,
  368. "logic": "AND",
  369. "conditions": []interface{}{
  370. map[string]interface{}{
  371. "path": "model",
  372. "mode": "prefix",
  373. "value": "gpt",
  374. },
  375. map[string]interface{}{
  376. "path": "temperature",
  377. "mode": "gt",
  378. "value": 0.5,
  379. },
  380. },
  381. },
  382. },
  383. }
  384. out, err := ApplyParamOverride(input, override, nil)
  385. if err != nil {
  386. t.Fatalf("ApplyParamOverride returned error: %v", err)
  387. }
  388. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(out))
  389. }
  390. func TestApplyParamOverrideConditionInvert(t *testing.T) {
  391. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  392. override := map[string]interface{}{
  393. "operations": []interface{}{
  394. map[string]interface{}{
  395. "path": "temperature",
  396. "mode": "set",
  397. "value": 0.1,
  398. "conditions": []interface{}{
  399. map[string]interface{}{
  400. "path": "model",
  401. "mode": "prefix",
  402. "value": "gpt",
  403. "invert": true,
  404. },
  405. },
  406. },
  407. },
  408. }
  409. out, err := ApplyParamOverride(input, override, nil)
  410. if err != nil {
  411. t.Fatalf("ApplyParamOverride returned error: %v", err)
  412. }
  413. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  414. }
  415. func TestApplyParamOverrideConditionPassMissingKey(t *testing.T) {
  416. input := []byte(`{"temperature":0.7}`)
  417. override := map[string]interface{}{
  418. "operations": []interface{}{
  419. map[string]interface{}{
  420. "path": "temperature",
  421. "mode": "set",
  422. "value": 0.1,
  423. "conditions": []interface{}{
  424. map[string]interface{}{
  425. "path": "model",
  426. "mode": "prefix",
  427. "value": "gpt",
  428. "pass_missing_key": true,
  429. },
  430. },
  431. },
  432. },
  433. }
  434. out, err := ApplyParamOverride(input, override, nil)
  435. if err != nil {
  436. t.Fatalf("ApplyParamOverride returned error: %v", err)
  437. }
  438. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  439. }
  440. func TestApplyParamOverrideConditionFromContext(t *testing.T) {
  441. input := []byte(`{"temperature":0.7}`)
  442. override := map[string]interface{}{
  443. "operations": []interface{}{
  444. map[string]interface{}{
  445. "path": "temperature",
  446. "mode": "set",
  447. "value": 0.1,
  448. "conditions": []interface{}{
  449. map[string]interface{}{
  450. "path": "model",
  451. "mode": "prefix",
  452. "value": "gpt",
  453. },
  454. },
  455. },
  456. },
  457. }
  458. ctx := map[string]interface{}{
  459. "model": "gpt-4",
  460. }
  461. out, err := ApplyParamOverride(input, override, ctx)
  462. if err != nil {
  463. t.Fatalf("ApplyParamOverride returned error: %v", err)
  464. }
  465. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  466. }
  467. func TestApplyParamOverrideNegativeIndexPath(t *testing.T) {
  468. input := []byte(`{"arr":[{"model":"a"},{"model":"b"}]}`)
  469. override := map[string]interface{}{
  470. "operations": []interface{}{
  471. map[string]interface{}{
  472. "path": "arr.-1.model",
  473. "mode": "set",
  474. "value": "c",
  475. },
  476. },
  477. }
  478. out, err := ApplyParamOverride(input, override, nil)
  479. if err != nil {
  480. t.Fatalf("ApplyParamOverride returned error: %v", err)
  481. }
  482. assertJSONEqual(t, `{"arr":[{"model":"a"},{"model":"c"}]}`, string(out))
  483. }
  484. func TestApplyParamOverrideRegexReplaceInvalidPattern(t *testing.T) {
  485. // regex_replace invalid pattern example:
  486. // {"operations":[{"path":"model","mode":"regex_replace","from":"(","to":"x"}]}
  487. input := []byte(`{"model":"gpt-4"}`)
  488. override := map[string]interface{}{
  489. "operations": []interface{}{
  490. map[string]interface{}{
  491. "path": "model",
  492. "mode": "regex_replace",
  493. "from": "(",
  494. "to": "x",
  495. },
  496. },
  497. }
  498. _, err := ApplyParamOverride(input, override, nil)
  499. if err == nil {
  500. t.Fatalf("expected error, got nil")
  501. }
  502. }
  503. func TestApplyParamOverrideCopy(t *testing.T) {
  504. // copy example:
  505. // {"operations":[{"mode":"copy","from":"model","to":"original_model"}]}
  506. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  507. override := map[string]interface{}{
  508. "operations": []interface{}{
  509. map[string]interface{}{
  510. "mode": "copy",
  511. "from": "model",
  512. "to": "original_model",
  513. },
  514. },
  515. }
  516. out, err := ApplyParamOverride(input, override, nil)
  517. if err != nil {
  518. t.Fatalf("ApplyParamOverride returned error: %v", err)
  519. }
  520. assertJSONEqual(t, `{"model":"gpt-4","original_model":"gpt-4","temperature":0.7}`, string(out))
  521. }
  522. func TestApplyParamOverrideCopyMissingSource(t *testing.T) {
  523. // copy missing source example:
  524. // {"operations":[{"mode":"copy","from":"model","to":"original_model"}]}
  525. input := []byte(`{"temperature":0.7}`)
  526. override := map[string]interface{}{
  527. "operations": []interface{}{
  528. map[string]interface{}{
  529. "mode": "copy",
  530. "from": "model",
  531. "to": "original_model",
  532. },
  533. },
  534. }
  535. _, err := ApplyParamOverride(input, override, nil)
  536. if err == nil {
  537. t.Fatalf("expected error, got nil")
  538. }
  539. }
  540. func TestApplyParamOverrideCopyRequiresFromTo(t *testing.T) {
  541. // copy requires from/to example:
  542. // {"operations":[{"mode":"copy"}]}
  543. input := []byte(`{"model":"gpt-4"}`)
  544. override := map[string]interface{}{
  545. "operations": []interface{}{
  546. map[string]interface{}{
  547. "mode": "copy",
  548. },
  549. },
  550. }
  551. _, err := ApplyParamOverride(input, override, nil)
  552. if err == nil {
  553. t.Fatalf("expected error, got nil")
  554. }
  555. }
  556. func TestApplyParamOverrideEnsurePrefix(t *testing.T) {
  557. // ensure_prefix example:
  558. // {"operations":[{"path":"model","mode":"ensure_prefix","value":"openai/"}]}
  559. input := []byte(`{"model":"gpt-4"}`)
  560. override := map[string]interface{}{
  561. "operations": []interface{}{
  562. map[string]interface{}{
  563. "path": "model",
  564. "mode": "ensure_prefix",
  565. "value": "openai/",
  566. },
  567. },
  568. }
  569. out, err := ApplyParamOverride(input, override, nil)
  570. if err != nil {
  571. t.Fatalf("ApplyParamOverride returned error: %v", err)
  572. }
  573. assertJSONEqual(t, `{"model":"openai/gpt-4"}`, string(out))
  574. }
  575. func TestApplyParamOverrideEnsurePrefixNoop(t *testing.T) {
  576. // ensure_prefix no-op example:
  577. // {"operations":[{"path":"model","mode":"ensure_prefix","value":"openai/"}]}
  578. input := []byte(`{"model":"openai/gpt-4"}`)
  579. override := map[string]interface{}{
  580. "operations": []interface{}{
  581. map[string]interface{}{
  582. "path": "model",
  583. "mode": "ensure_prefix",
  584. "value": "openai/",
  585. },
  586. },
  587. }
  588. out, err := ApplyParamOverride(input, override, nil)
  589. if err != nil {
  590. t.Fatalf("ApplyParamOverride returned error: %v", err)
  591. }
  592. assertJSONEqual(t, `{"model":"openai/gpt-4"}`, string(out))
  593. }
  594. func TestApplyParamOverrideEnsureSuffix(t *testing.T) {
  595. // ensure_suffix example:
  596. // {"operations":[{"path":"model","mode":"ensure_suffix","value":"-latest"}]}
  597. input := []byte(`{"model":"gpt-4"}`)
  598. override := map[string]interface{}{
  599. "operations": []interface{}{
  600. map[string]interface{}{
  601. "path": "model",
  602. "mode": "ensure_suffix",
  603. "value": "-latest",
  604. },
  605. },
  606. }
  607. out, err := ApplyParamOverride(input, override, nil)
  608. if err != nil {
  609. t.Fatalf("ApplyParamOverride returned error: %v", err)
  610. }
  611. assertJSONEqual(t, `{"model":"gpt-4-latest"}`, string(out))
  612. }
  613. func TestApplyParamOverrideEnsureSuffixNoop(t *testing.T) {
  614. // ensure_suffix no-op example:
  615. // {"operations":[{"path":"model","mode":"ensure_suffix","value":"-latest"}]}
  616. input := []byte(`{"model":"gpt-4-latest"}`)
  617. override := map[string]interface{}{
  618. "operations": []interface{}{
  619. map[string]interface{}{
  620. "path": "model",
  621. "mode": "ensure_suffix",
  622. "value": "-latest",
  623. },
  624. },
  625. }
  626. out, err := ApplyParamOverride(input, override, nil)
  627. if err != nil {
  628. t.Fatalf("ApplyParamOverride returned error: %v", err)
  629. }
  630. assertJSONEqual(t, `{"model":"gpt-4-latest"}`, string(out))
  631. }
  632. func TestApplyParamOverrideEnsureRequiresValue(t *testing.T) {
  633. // ensure_prefix requires value example:
  634. // {"operations":[{"path":"model","mode":"ensure_prefix"}]}
  635. input := []byte(`{"model":"gpt-4"}`)
  636. override := map[string]interface{}{
  637. "operations": []interface{}{
  638. map[string]interface{}{
  639. "path": "model",
  640. "mode": "ensure_prefix",
  641. },
  642. },
  643. }
  644. _, err := ApplyParamOverride(input, override, nil)
  645. if err == nil {
  646. t.Fatalf("expected error, got nil")
  647. }
  648. }
  649. func TestApplyParamOverrideTrimSpace(t *testing.T) {
  650. // trim_space example:
  651. // {"operations":[{"path":"model","mode":"trim_space"}]}
  652. input := []byte("{\"model\":\" gpt-4 \\n\"}")
  653. override := map[string]interface{}{
  654. "operations": []interface{}{
  655. map[string]interface{}{
  656. "path": "model",
  657. "mode": "trim_space",
  658. },
  659. },
  660. }
  661. out, err := ApplyParamOverride(input, override, nil)
  662. if err != nil {
  663. t.Fatalf("ApplyParamOverride returned error: %v", err)
  664. }
  665. assertJSONEqual(t, `{"model":"gpt-4"}`, string(out))
  666. }
  667. func TestApplyParamOverrideToLower(t *testing.T) {
  668. // to_lower example:
  669. // {"operations":[{"path":"model","mode":"to_lower"}]}
  670. input := []byte(`{"model":"GPT-4"}`)
  671. override := map[string]interface{}{
  672. "operations": []interface{}{
  673. map[string]interface{}{
  674. "path": "model",
  675. "mode": "to_lower",
  676. },
  677. },
  678. }
  679. out, err := ApplyParamOverride(input, override, nil)
  680. if err != nil {
  681. t.Fatalf("ApplyParamOverride returned error: %v", err)
  682. }
  683. assertJSONEqual(t, `{"model":"gpt-4"}`, string(out))
  684. }
  685. func TestApplyParamOverrideToUpper(t *testing.T) {
  686. // to_upper example:
  687. // {"operations":[{"path":"model","mode":"to_upper"}]}
  688. input := []byte(`{"model":"gpt-4"}`)
  689. override := map[string]interface{}{
  690. "operations": []interface{}{
  691. map[string]interface{}{
  692. "path": "model",
  693. "mode": "to_upper",
  694. },
  695. },
  696. }
  697. out, err := ApplyParamOverride(input, override, nil)
  698. if err != nil {
  699. t.Fatalf("ApplyParamOverride returned error: %v", err)
  700. }
  701. assertJSONEqual(t, `{"model":"GPT-4"}`, string(out))
  702. }
  703. func TestApplyParamOverrideReturnError(t *testing.T) {
  704. input := []byte(`{"model":"gemini-2.5-pro"}`)
  705. override := map[string]interface{}{
  706. "operations": []interface{}{
  707. map[string]interface{}{
  708. "mode": "return_error",
  709. "value": map[string]interface{}{
  710. "message": "forced bad request by param override",
  711. "status_code": 422,
  712. "code": "forced_bad_request",
  713. "type": "invalid_request_error",
  714. "skip_retry": true,
  715. },
  716. "conditions": []interface{}{
  717. map[string]interface{}{
  718. "path": "retry.is_retry",
  719. "mode": "full",
  720. "value": true,
  721. },
  722. },
  723. },
  724. },
  725. }
  726. ctx := map[string]interface{}{
  727. "retry": map[string]interface{}{
  728. "index": 1,
  729. "is_retry": true,
  730. },
  731. }
  732. _, err := ApplyParamOverride(input, override, ctx)
  733. if err == nil {
  734. t.Fatalf("expected error, got nil")
  735. }
  736. returnErr, ok := AsParamOverrideReturnError(err)
  737. if !ok {
  738. t.Fatalf("expected ParamOverrideReturnError, got %T: %v", err, err)
  739. }
  740. if returnErr.StatusCode != 422 {
  741. t.Fatalf("expected status 422, got %d", returnErr.StatusCode)
  742. }
  743. if returnErr.Code != "forced_bad_request" {
  744. t.Fatalf("expected code forced_bad_request, got %s", returnErr.Code)
  745. }
  746. if !returnErr.SkipRetry {
  747. t.Fatalf("expected skip_retry true")
  748. }
  749. }
  750. func TestApplyParamOverridePruneObjectsByTypeString(t *testing.T) {
  751. input := []byte(`{
  752. "messages":[
  753. {"role":"assistant","content":[
  754. {"type":"output_text","text":"a"},
  755. {"type":"redacted_thinking","text":"secret"},
  756. {"type":"tool_call","name":"tool_a"}
  757. ]},
  758. {"role":"assistant","content":[
  759. {"type":"output_text","text":"b"},
  760. {"type":"wrapper","parts":[
  761. {"type":"redacted_thinking","text":"secret2"},
  762. {"type":"output_text","text":"c"}
  763. ]}
  764. ]}
  765. ]
  766. }`)
  767. override := map[string]interface{}{
  768. "operations": []interface{}{
  769. map[string]interface{}{
  770. "mode": "prune_objects",
  771. "value": "redacted_thinking",
  772. },
  773. },
  774. }
  775. out, err := ApplyParamOverride(input, override, nil)
  776. if err != nil {
  777. t.Fatalf("ApplyParamOverride returned error: %v", err)
  778. }
  779. assertJSONEqual(t, `{
  780. "messages":[
  781. {"role":"assistant","content":[
  782. {"type":"output_text","text":"a"},
  783. {"type":"tool_call","name":"tool_a"}
  784. ]},
  785. {"role":"assistant","content":[
  786. {"type":"output_text","text":"b"},
  787. {"type":"wrapper","parts":[
  788. {"type":"output_text","text":"c"}
  789. ]}
  790. ]}
  791. ]
  792. }`, string(out))
  793. }
  794. func TestApplyParamOverridePruneObjectsWhereAndPath(t *testing.T) {
  795. input := []byte(`{
  796. "a":{"items":[{"type":"redacted_thinking","id":1},{"type":"output_text","id":2}]},
  797. "b":{"items":[{"type":"redacted_thinking","id":3},{"type":"output_text","id":4}]}
  798. }`)
  799. override := map[string]interface{}{
  800. "operations": []interface{}{
  801. map[string]interface{}{
  802. "path": "a",
  803. "mode": "prune_objects",
  804. "value": map[string]interface{}{
  805. "where": map[string]interface{}{
  806. "type": "redacted_thinking",
  807. },
  808. },
  809. },
  810. },
  811. }
  812. out, err := ApplyParamOverride(input, override, nil)
  813. if err != nil {
  814. t.Fatalf("ApplyParamOverride returned error: %v", err)
  815. }
  816. assertJSONEqual(t, `{
  817. "a":{"items":[{"type":"output_text","id":2}]},
  818. "b":{"items":[{"type":"redacted_thinking","id":3},{"type":"output_text","id":4}]}
  819. }`, string(out))
  820. }
  821. func TestApplyParamOverrideNormalizeThinkingSignatureUnsupported(t *testing.T) {
  822. input := []byte(`{"items":[{"type":"redacted_thinking"}]}`)
  823. override := map[string]interface{}{
  824. "operations": []interface{}{
  825. map[string]interface{}{
  826. "mode": "normalize_thinking_signature",
  827. },
  828. },
  829. }
  830. _, err := ApplyParamOverride(input, override, nil)
  831. if err == nil {
  832. t.Fatalf("expected error, got nil")
  833. }
  834. }
  835. func TestApplyParamOverrideConditionFromRetryAndLastErrorContext(t *testing.T) {
  836. info := &RelayInfo{
  837. RetryIndex: 1,
  838. LastError: types.WithOpenAIError(types.OpenAIError{
  839. Message: "invalid thinking signature",
  840. Type: "invalid_request_error",
  841. Code: "bad_thought_signature",
  842. }, 400),
  843. }
  844. ctx := BuildParamOverrideContext(info)
  845. input := []byte(`{"temperature":0.7}`)
  846. override := map[string]interface{}{
  847. "operations": []interface{}{
  848. map[string]interface{}{
  849. "path": "temperature",
  850. "mode": "set",
  851. "value": 0.1,
  852. "logic": "AND",
  853. "conditions": []interface{}{
  854. map[string]interface{}{
  855. "path": "is_retry",
  856. "mode": "full",
  857. "value": true,
  858. },
  859. map[string]interface{}{
  860. "path": "last_error.code",
  861. "mode": "contains",
  862. "value": "thought_signature",
  863. },
  864. },
  865. },
  866. },
  867. }
  868. out, err := ApplyParamOverride(input, override, ctx)
  869. if err != nil {
  870. t.Fatalf("ApplyParamOverride returned error: %v", err)
  871. }
  872. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  873. }
  874. func assertJSONEqual(t *testing.T, want, got string) {
  875. t.Helper()
  876. var wantObj interface{}
  877. var gotObj interface{}
  878. if err := json.Unmarshal([]byte(want), &wantObj); err != nil {
  879. t.Fatalf("failed to unmarshal want JSON: %v", err)
  880. }
  881. if err := json.Unmarshal([]byte(got), &gotObj); err != nil {
  882. t.Fatalf("failed to unmarshal got JSON: %v", err)
  883. }
  884. if !reflect.DeepEqual(wantObj, gotObj) {
  885. t.Fatalf("json not equal\nwant: %s\ngot: %s", want, got)
  886. }
  887. }