@@ -387,6 +387,78 @@ var _ = Describe("Mock Backend E2E Tests", Label("MockBackend"), func() {
387387 })
388388 })
389389
390+ Describe ("Detokenization API" , func () {
391+ It ("should return content for known token IDs" , func () {
392+ body := `{"model":"mock-model","tokens":[101,2023,2003,1037,3231,1012]}`
393+ req , err := http .NewRequest ("POST" , apiURL + "/detokenize" , strings .NewReader (body ))
394+ Expect (err ).ToNot (HaveOccurred ())
395+ req .Header .Set ("Content-Type" , "application/json" )
396+
397+ httpClient := & http.Client {Timeout : 30 * time .Second }
398+ resp , err := httpClient .Do (req )
399+ Expect (err ).ToNot (HaveOccurred ())
400+ defer resp .Body .Close ()
401+ Expect (resp .StatusCode ).To (Equal (200 ))
402+
403+ data , err := io .ReadAll (resp .Body )
404+ Expect (err ).ToNot (HaveOccurred ())
405+ var result map [string ]any
406+ Expect (json .Unmarshal (data , & result )).To (Succeed ())
407+ content , ok := result ["content" ].(string )
408+ Expect (ok ).To (BeTrue (), "response missing 'content' field: %s" , string (data ))
409+ Expect (content ).ToNot (BeEmpty ())
410+ })
411+
412+ It ("should round-trip tokenize then detokenize" , func () {
413+ httpClient := & http.Client {Timeout : 30 * time .Second }
414+
415+ // Step 1: tokenize
416+ tokenizeReq , err := http .NewRequest ("POST" , apiURL + "/tokenize" ,
417+ strings .NewReader (`{"model":"mock-model","content":"Hello world"}` ))
418+ Expect (err ).ToNot (HaveOccurred ())
419+ tokenizeReq .Header .Set ("Content-Type" , "application/json" )
420+
421+ tokenizeResp , err := httpClient .Do (tokenizeReq )
422+ Expect (err ).ToNot (HaveOccurred ())
423+ defer tokenizeResp .Body .Close ()
424+ Expect (tokenizeResp .StatusCode ).To (Equal (200 ))
425+
426+ tokenizeData , err := io .ReadAll (tokenizeResp .Body )
427+ Expect (err ).ToNot (HaveOccurred ())
428+ var tokenizeResult map [string ]any
429+ Expect (json .Unmarshal (tokenizeData , & tokenizeResult )).To (Succeed ())
430+
431+ tokensRaw , ok := tokenizeResult ["tokens" ].([]any )
432+ Expect (ok ).To (BeTrue (), "tokenize response missing 'tokens': %s" , string (tokenizeData ))
433+ Expect (tokensRaw ).ToNot (BeEmpty ())
434+
435+ // Step 2: detokenize the returned token IDs
436+ tokens := make ([]int , len (tokensRaw ))
437+ for i , t := range tokensRaw {
438+ tokens [i ] = int (t .(float64 ))
439+ }
440+ tokenJSON , err := json .Marshal (map [string ]any {"model" : "mock-model" , "tokens" : tokens })
441+ Expect (err ).ToNot (HaveOccurred ())
442+
443+ detokenizeReq , err := http .NewRequest ("POST" , apiURL + "/detokenize" , strings .NewReader (string (tokenJSON )))
444+ Expect (err ).ToNot (HaveOccurred ())
445+ detokenizeReq .Header .Set ("Content-Type" , "application/json" )
446+
447+ detokenizeResp , err := httpClient .Do (detokenizeReq )
448+ Expect (err ).ToNot (HaveOccurred ())
449+ defer detokenizeResp .Body .Close ()
450+ Expect (detokenizeResp .StatusCode ).To (Equal (200 ))
451+
452+ detokenizeData , err := io .ReadAll (detokenizeResp .Body )
453+ Expect (err ).ToNot (HaveOccurred ())
454+ var detokenizeResult map [string ]any
455+ Expect (json .Unmarshal (detokenizeData , & detokenizeResult )).To (Succeed ())
456+ content , ok := detokenizeResult ["content" ].(string )
457+ Expect (ok ).To (BeTrue (), "detokenize response missing 'content': %s" , string (detokenizeData ))
458+ Expect (content ).ToNot (BeEmpty ())
459+ })
460+ })
461+
390462 Describe ("Autoparser ChatDelta Streaming" , Label ("Autoparser" ), func () {
391463 // These tests verify that when the C++ autoparser handles tool calls
392464 // and content via ChatDeltas (with empty raw message), the streaming
0 commit comments