mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
feat(translator): add token counting functionality for Gemini, Claude, and CLI - Introduced `TokenCount` handling across various Codex translators (Gemini, Claude, CLI) with respective implementations. - Added utility methods for token counting and formatting responses. - Integrated `tiktoken-go/tokenizer` library for tokenization. - Updated CodexExecutor with token counting logic to support multiple models including GPT-5 variants. - Refined go.mod and go.sum to include new dependencies. feat(runtime): add token counting functionality across executors - Implemented token counting in OpenAICompatExecutor, QwenExecutor, and IFlowExecutor. - Added utilities for token counting and response formatting using `tiktoken-go/tokenizer`. - Integrated token counting into translators for Gemini, Claude, and Gemini CLI. - Enhanced multiple model support, including GPT-5 variants, for token counting. docs: update environment variable instructions for multi-model support - Added details for setting `ANTHROPIC_DEFAULT_OPUS_MODEL`, `ANTHROPIC_DEFAULT_SONNET_MODEL`, and `ANTHROPIC_DEFAULT_HAIKU_MODEL` for version 2.x.x. - Clarified usage of `ANTHROPIC_MODEL` and `ANTHROPIC_SMALL_FAST_MODEL` for version 1.x.x. - Expanded examples for setting environment variables across different models including Gemini, GPT-5, Claude, and Qwen3.
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
"github.com/tiktoken-go/tokenizer"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
@@ -277,7 +278,180 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
|
||||
}
|
||||
|
||||
func (e *CodexExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
||||
return cliproxyexecutor.Response{Payload: []byte{}}, fmt.Errorf("not implemented")
|
||||
from := opts.SourceFormat
|
||||
to := sdktranslator.FromString("codex")
|
||||
body := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false)
|
||||
|
||||
modelForCounting := req.Model
|
||||
|
||||
if util.InArray([]string{"gpt-5", "gpt-5-minimal", "gpt-5-low", "gpt-5-medium", "gpt-5-high"}, req.Model) {
|
||||
modelForCounting = "gpt-5"
|
||||
body, _ = sjson.SetBytes(body, "model", "gpt-5")
|
||||
switch req.Model {
|
||||
case "gpt-5-minimal":
|
||||
body, _ = sjson.SetBytes(body, "reasoning.effort", "minimal")
|
||||
case "gpt-5-low":
|
||||
body, _ = sjson.SetBytes(body, "reasoning.effort", "low")
|
||||
case "gpt-5-medium":
|
||||
body, _ = sjson.SetBytes(body, "reasoning.effort", "medium")
|
||||
case "gpt-5-high":
|
||||
body, _ = sjson.SetBytes(body, "reasoning.effort", "high")
|
||||
default:
|
||||
body, _ = sjson.SetBytes(body, "reasoning.effort", "low")
|
||||
}
|
||||
} else if util.InArray([]string{"gpt-5-codex", "gpt-5-codex-low", "gpt-5-codex-medium", "gpt-5-codex-high"}, req.Model) {
|
||||
modelForCounting = "gpt-5"
|
||||
body, _ = sjson.SetBytes(body, "model", "gpt-5-codex")
|
||||
switch req.Model {
|
||||
case "gpt-5-codex-low":
|
||||
body, _ = sjson.SetBytes(body, "reasoning.effort", "low")
|
||||
case "gpt-5-codex-medium":
|
||||
body, _ = sjson.SetBytes(body, "reasoning.effort", "medium")
|
||||
case "gpt-5-codex-high":
|
||||
body, _ = sjson.SetBytes(body, "reasoning.effort", "high")
|
||||
default:
|
||||
body, _ = sjson.SetBytes(body, "reasoning.effort", "low")
|
||||
}
|
||||
}
|
||||
|
||||
body, _ = sjson.DeleteBytes(body, "previous_response_id")
|
||||
body, _ = sjson.SetBytes(body, "stream", false)
|
||||
|
||||
enc, err := tokenizerForCodexModel(modelForCounting)
|
||||
if err != nil {
|
||||
return cliproxyexecutor.Response{}, fmt.Errorf("codex executor: tokenizer init failed: %w", err)
|
||||
}
|
||||
|
||||
count, err := countCodexInputTokens(enc, body)
|
||||
if err != nil {
|
||||
return cliproxyexecutor.Response{}, fmt.Errorf("codex executor: token counting failed: %w", err)
|
||||
}
|
||||
|
||||
usageJSON := fmt.Sprintf(`{"response":{"usage":{"input_tokens":%d,"output_tokens":0,"total_tokens":%d}}}`, count, count)
|
||||
translated := sdktranslator.TranslateTokenCount(ctx, to, from, count, []byte(usageJSON))
|
||||
return cliproxyexecutor.Response{Payload: []byte(translated)}, nil
|
||||
}
|
||||
|
||||
func tokenizerForCodexModel(model string) (tokenizer.Codec, error) {
|
||||
sanitized := strings.ToLower(strings.TrimSpace(model))
|
||||
switch {
|
||||
case sanitized == "":
|
||||
return tokenizer.Get(tokenizer.Cl100kBase)
|
||||
case strings.HasPrefix(sanitized, "gpt-5"):
|
||||
return tokenizer.ForModel(tokenizer.GPT5)
|
||||
case strings.HasPrefix(sanitized, "gpt-4.1"):
|
||||
return tokenizer.ForModel(tokenizer.GPT41)
|
||||
case strings.HasPrefix(sanitized, "gpt-4o"):
|
||||
return tokenizer.ForModel(tokenizer.GPT4o)
|
||||
case strings.HasPrefix(sanitized, "gpt-4"):
|
||||
return tokenizer.ForModel(tokenizer.GPT4)
|
||||
case strings.HasPrefix(sanitized, "gpt-3.5"), strings.HasPrefix(sanitized, "gpt-3"):
|
||||
return tokenizer.ForModel(tokenizer.GPT35Turbo)
|
||||
default:
|
||||
return tokenizer.Get(tokenizer.Cl100kBase)
|
||||
}
|
||||
}
|
||||
|
||||
func countCodexInputTokens(enc tokenizer.Codec, body []byte) (int64, error) {
|
||||
if enc == nil {
|
||||
return 0, fmt.Errorf("encoder is nil")
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
root := gjson.ParseBytes(body)
|
||||
var segments []string
|
||||
|
||||
if inst := strings.TrimSpace(root.Get("instructions").String()); inst != "" {
|
||||
segments = append(segments, inst)
|
||||
}
|
||||
|
||||
inputItems := root.Get("input")
|
||||
if inputItems.IsArray() {
|
||||
arr := inputItems.Array()
|
||||
for i := range arr {
|
||||
item := arr[i]
|
||||
switch item.Get("type").String() {
|
||||
case "message":
|
||||
content := item.Get("content")
|
||||
if content.IsArray() {
|
||||
parts := content.Array()
|
||||
for j := range parts {
|
||||
part := parts[j]
|
||||
if text := strings.TrimSpace(part.Get("text").String()); text != "" {
|
||||
segments = append(segments, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "function_call":
|
||||
if name := strings.TrimSpace(item.Get("name").String()); name != "" {
|
||||
segments = append(segments, name)
|
||||
}
|
||||
if args := strings.TrimSpace(item.Get("arguments").String()); args != "" {
|
||||
segments = append(segments, args)
|
||||
}
|
||||
case "function_call_output":
|
||||
if out := strings.TrimSpace(item.Get("output").String()); out != "" {
|
||||
segments = append(segments, out)
|
||||
}
|
||||
default:
|
||||
if text := strings.TrimSpace(item.Get("text").String()); text != "" {
|
||||
segments = append(segments, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tools := root.Get("tools")
|
||||
if tools.IsArray() {
|
||||
tarr := tools.Array()
|
||||
for i := range tarr {
|
||||
tool := tarr[i]
|
||||
if name := strings.TrimSpace(tool.Get("name").String()); name != "" {
|
||||
segments = append(segments, name)
|
||||
}
|
||||
if desc := strings.TrimSpace(tool.Get("description").String()); desc != "" {
|
||||
segments = append(segments, desc)
|
||||
}
|
||||
if params := tool.Get("parameters"); params.Exists() {
|
||||
val := params.Raw
|
||||
if params.Type == gjson.String {
|
||||
val = params.String()
|
||||
}
|
||||
if trimmed := strings.TrimSpace(val); trimmed != "" {
|
||||
segments = append(segments, trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textFormat := root.Get("text.format")
|
||||
if textFormat.Exists() {
|
||||
if name := strings.TrimSpace(textFormat.Get("name").String()); name != "" {
|
||||
segments = append(segments, name)
|
||||
}
|
||||
if schema := textFormat.Get("schema"); schema.Exists() {
|
||||
val := schema.Raw
|
||||
if schema.Type == gjson.String {
|
||||
val = schema.String()
|
||||
}
|
||||
if trimmed := strings.TrimSpace(val); trimmed != "" {
|
||||
segments = append(segments, trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text := strings.Join(segments, "\n")
|
||||
if text == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
count, err := enc.Count(text)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(count), nil
|
||||
}
|
||||
|
||||
func (e *CodexExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) {
|
||||
|
||||
@@ -221,9 +221,24 @@ func (e *IFlowExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
// CountTokens is not implemented for iFlow.
|
||||
func (e *IFlowExecutor) CountTokens(context.Context, *cliproxyauth.Auth, cliproxyexecutor.Request, cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
||||
return cliproxyexecutor.Response{Payload: nil}, fmt.Errorf("not implemented")
|
||||
func (e *IFlowExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
||||
from := opts.SourceFormat
|
||||
to := sdktranslator.FromString("openai")
|
||||
body := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false)
|
||||
|
||||
enc, err := tokenizerForModel(req.Model)
|
||||
if err != nil {
|
||||
return cliproxyexecutor.Response{}, fmt.Errorf("iflow executor: tokenizer init failed: %w", err)
|
||||
}
|
||||
|
||||
count, err := countOpenAIChatTokens(enc, body)
|
||||
if err != nil {
|
||||
return cliproxyexecutor.Response{}, fmt.Errorf("iflow executor: token counting failed: %w", err)
|
||||
}
|
||||
|
||||
usageJSON := buildOpenAIUsageJSON(count)
|
||||
translated := sdktranslator.TranslateTokenCount(ctx, to, from, count, usageJSON)
|
||||
return cliproxyexecutor.Response{Payload: []byte(translated)}, nil
|
||||
}
|
||||
|
||||
// Refresh refreshes OAuth tokens and updates the stored API key.
|
||||
|
||||
@@ -219,7 +219,29 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
|
||||
}
|
||||
|
||||
func (e *OpenAICompatExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
||||
return cliproxyexecutor.Response{Payload: []byte{}}, fmt.Errorf("not implemented")
|
||||
from := opts.SourceFormat
|
||||
to := sdktranslator.FromString("openai")
|
||||
translated := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false)
|
||||
|
||||
modelForCounting := req.Model
|
||||
if modelOverride := e.resolveUpstreamModel(req.Model, auth); modelOverride != "" {
|
||||
translated = e.overrideModel(translated, modelOverride)
|
||||
modelForCounting = modelOverride
|
||||
}
|
||||
|
||||
enc, err := tokenizerForModel(modelForCounting)
|
||||
if err != nil {
|
||||
return cliproxyexecutor.Response{}, fmt.Errorf("openai compat executor: tokenizer init failed: %w", err)
|
||||
}
|
||||
|
||||
count, err := countOpenAIChatTokens(enc, translated)
|
||||
if err != nil {
|
||||
return cliproxyexecutor.Response{}, fmt.Errorf("openai compat executor: token counting failed: %w", err)
|
||||
}
|
||||
|
||||
usageJSON := buildOpenAIUsageJSON(count)
|
||||
translatedUsage := sdktranslator.TranslateTokenCount(ctx, to, from, count, usageJSON)
|
||||
return cliproxyexecutor.Response{Payload: []byte(translatedUsage)}, nil
|
||||
}
|
||||
|
||||
// Refresh is a no-op for API-key based compatibility providers.
|
||||
|
||||
@@ -207,7 +207,28 @@ func (e *QwenExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Aut
|
||||
}
|
||||
|
||||
func (e *QwenExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
||||
return cliproxyexecutor.Response{Payload: []byte{}}, fmt.Errorf("not implemented")
|
||||
from := opts.SourceFormat
|
||||
to := sdktranslator.FromString("openai")
|
||||
body := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false)
|
||||
|
||||
modelName := gjson.GetBytes(body, "model").String()
|
||||
if strings.TrimSpace(modelName) == "" {
|
||||
modelName = req.Model
|
||||
}
|
||||
|
||||
enc, err := tokenizerForModel(modelName)
|
||||
if err != nil {
|
||||
return cliproxyexecutor.Response{}, fmt.Errorf("qwen executor: tokenizer init failed: %w", err)
|
||||
}
|
||||
|
||||
count, err := countOpenAIChatTokens(enc, body)
|
||||
if err != nil {
|
||||
return cliproxyexecutor.Response{}, fmt.Errorf("qwen executor: token counting failed: %w", err)
|
||||
}
|
||||
|
||||
usageJSON := buildOpenAIUsageJSON(count)
|
||||
translated := sdktranslator.TranslateTokenCount(ctx, to, from, count, usageJSON)
|
||||
return cliproxyexecutor.Response{Payload: []byte(translated)}, nil
|
||||
}
|
||||
|
||||
func (e *QwenExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) {
|
||||
|
||||
234
internal/runtime/executor/token_helpers.go
Normal file
234
internal/runtime/executor/token_helpers.go
Normal file
@@ -0,0 +1,234 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tiktoken-go/tokenizer"
|
||||
)
|
||||
|
||||
// tokenizerForModel returns a tokenizer codec suitable for an OpenAI-style model id.
|
||||
func tokenizerForModel(model string) (tokenizer.Codec, error) {
|
||||
sanitized := strings.ToLower(strings.TrimSpace(model))
|
||||
switch {
|
||||
case sanitized == "":
|
||||
return tokenizer.Get(tokenizer.Cl100kBase)
|
||||
case strings.HasPrefix(sanitized, "gpt-5"):
|
||||
return tokenizer.ForModel(tokenizer.GPT5)
|
||||
case strings.HasPrefix(sanitized, "gpt-4.1"):
|
||||
return tokenizer.ForModel(tokenizer.GPT41)
|
||||
case strings.HasPrefix(sanitized, "gpt-4o"):
|
||||
return tokenizer.ForModel(tokenizer.GPT4o)
|
||||
case strings.HasPrefix(sanitized, "gpt-4"):
|
||||
return tokenizer.ForModel(tokenizer.GPT4)
|
||||
case strings.HasPrefix(sanitized, "gpt-3.5"), strings.HasPrefix(sanitized, "gpt-3"):
|
||||
return tokenizer.ForModel(tokenizer.GPT35Turbo)
|
||||
case strings.HasPrefix(sanitized, "o1"):
|
||||
return tokenizer.ForModel(tokenizer.O1)
|
||||
case strings.HasPrefix(sanitized, "o3"):
|
||||
return tokenizer.ForModel(tokenizer.O3)
|
||||
case strings.HasPrefix(sanitized, "o4"):
|
||||
return tokenizer.ForModel(tokenizer.O4Mini)
|
||||
default:
|
||||
return tokenizer.Get(tokenizer.O200kBase)
|
||||
}
|
||||
}
|
||||
|
||||
// countOpenAIChatTokens approximates prompt tokens for OpenAI chat completions payloads.
|
||||
func countOpenAIChatTokens(enc tokenizer.Codec, payload []byte) (int64, error) {
|
||||
if enc == nil {
|
||||
return 0, fmt.Errorf("encoder is nil")
|
||||
}
|
||||
if len(payload) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
root := gjson.ParseBytes(payload)
|
||||
segments := make([]string, 0, 32)
|
||||
|
||||
collectOpenAIMessages(root.Get("messages"), &segments)
|
||||
collectOpenAITools(root.Get("tools"), &segments)
|
||||
collectOpenAIFunctions(root.Get("functions"), &segments)
|
||||
collectOpenAIToolChoice(root.Get("tool_choice"), &segments)
|
||||
collectOpenAIResponseFormat(root.Get("response_format"), &segments)
|
||||
addIfNotEmpty(&segments, root.Get("input").String())
|
||||
addIfNotEmpty(&segments, root.Get("prompt").String())
|
||||
|
||||
joined := strings.TrimSpace(strings.Join(segments, "\n"))
|
||||
if joined == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
count, err := enc.Count(joined)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(count), nil
|
||||
}
|
||||
|
||||
// buildOpenAIUsageJSON returns a minimal usage structure understood by downstream translators.
|
||||
func buildOpenAIUsageJSON(count int64) []byte {
|
||||
return []byte(fmt.Sprintf(`{"usage":{"prompt_tokens":%d,"completion_tokens":0,"total_tokens":%d}}`, count, count))
|
||||
}
|
||||
|
||||
func collectOpenAIMessages(messages gjson.Result, segments *[]string) {
|
||||
if !messages.Exists() || !messages.IsArray() {
|
||||
return
|
||||
}
|
||||
messages.ForEach(func(_, message gjson.Result) bool {
|
||||
addIfNotEmpty(segments, message.Get("role").String())
|
||||
addIfNotEmpty(segments, message.Get("name").String())
|
||||
collectOpenAIContent(message.Get("content"), segments)
|
||||
collectOpenAIToolCalls(message.Get("tool_calls"), segments)
|
||||
collectOpenAIFunctionCall(message.Get("function_call"), segments)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func collectOpenAIContent(content gjson.Result, segments *[]string) {
|
||||
if !content.Exists() {
|
||||
return
|
||||
}
|
||||
if content.Type == gjson.String {
|
||||
addIfNotEmpty(segments, content.String())
|
||||
return
|
||||
}
|
||||
if content.IsArray() {
|
||||
content.ForEach(func(_, part gjson.Result) bool {
|
||||
partType := part.Get("type").String()
|
||||
switch partType {
|
||||
case "text", "input_text", "output_text":
|
||||
addIfNotEmpty(segments, part.Get("text").String())
|
||||
case "image_url":
|
||||
addIfNotEmpty(segments, part.Get("image_url.url").String())
|
||||
case "input_audio", "output_audio", "audio":
|
||||
addIfNotEmpty(segments, part.Get("id").String())
|
||||
case "tool_result":
|
||||
addIfNotEmpty(segments, part.Get("name").String())
|
||||
collectOpenAIContent(part.Get("content"), segments)
|
||||
default:
|
||||
if part.IsArray() {
|
||||
collectOpenAIContent(part, segments)
|
||||
return true
|
||||
}
|
||||
if part.Type == gjson.JSON {
|
||||
addIfNotEmpty(segments, part.Raw)
|
||||
return true
|
||||
}
|
||||
addIfNotEmpty(segments, part.String())
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
if content.Type == gjson.JSON {
|
||||
addIfNotEmpty(segments, content.Raw)
|
||||
}
|
||||
}
|
||||
|
||||
func collectOpenAIToolCalls(calls gjson.Result, segments *[]string) {
|
||||
if !calls.Exists() || !calls.IsArray() {
|
||||
return
|
||||
}
|
||||
calls.ForEach(func(_, call gjson.Result) bool {
|
||||
addIfNotEmpty(segments, call.Get("id").String())
|
||||
addIfNotEmpty(segments, call.Get("type").String())
|
||||
function := call.Get("function")
|
||||
if function.Exists() {
|
||||
addIfNotEmpty(segments, function.Get("name").String())
|
||||
addIfNotEmpty(segments, function.Get("description").String())
|
||||
addIfNotEmpty(segments, function.Get("arguments").String())
|
||||
if params := function.Get("parameters"); params.Exists() {
|
||||
addIfNotEmpty(segments, params.Raw)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func collectOpenAIFunctionCall(call gjson.Result, segments *[]string) {
|
||||
if !call.Exists() {
|
||||
return
|
||||
}
|
||||
addIfNotEmpty(segments, call.Get("name").String())
|
||||
addIfNotEmpty(segments, call.Get("arguments").String())
|
||||
}
|
||||
|
||||
func collectOpenAITools(tools gjson.Result, segments *[]string) {
|
||||
if !tools.Exists() {
|
||||
return
|
||||
}
|
||||
if tools.IsArray() {
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
appendToolPayload(tool, segments)
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
appendToolPayload(tools, segments)
|
||||
}
|
||||
|
||||
func collectOpenAIFunctions(functions gjson.Result, segments *[]string) {
|
||||
if !functions.Exists() || !functions.IsArray() {
|
||||
return
|
||||
}
|
||||
functions.ForEach(func(_, function gjson.Result) bool {
|
||||
addIfNotEmpty(segments, function.Get("name").String())
|
||||
addIfNotEmpty(segments, function.Get("description").String())
|
||||
if params := function.Get("parameters"); params.Exists() {
|
||||
addIfNotEmpty(segments, params.Raw)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func collectOpenAIToolChoice(choice gjson.Result, segments *[]string) {
|
||||
if !choice.Exists() {
|
||||
return
|
||||
}
|
||||
if choice.Type == gjson.String {
|
||||
addIfNotEmpty(segments, choice.String())
|
||||
return
|
||||
}
|
||||
addIfNotEmpty(segments, choice.Raw)
|
||||
}
|
||||
|
||||
func collectOpenAIResponseFormat(format gjson.Result, segments *[]string) {
|
||||
if !format.Exists() {
|
||||
return
|
||||
}
|
||||
addIfNotEmpty(segments, format.Get("type").String())
|
||||
addIfNotEmpty(segments, format.Get("name").String())
|
||||
if schema := format.Get("json_schema"); schema.Exists() {
|
||||
addIfNotEmpty(segments, schema.Raw)
|
||||
}
|
||||
if schema := format.Get("schema"); schema.Exists() {
|
||||
addIfNotEmpty(segments, schema.Raw)
|
||||
}
|
||||
}
|
||||
|
||||
func appendToolPayload(tool gjson.Result, segments *[]string) {
|
||||
if !tool.Exists() {
|
||||
return
|
||||
}
|
||||
addIfNotEmpty(segments, tool.Get("type").String())
|
||||
addIfNotEmpty(segments, tool.Get("name").String())
|
||||
addIfNotEmpty(segments, tool.Get("description").String())
|
||||
if function := tool.Get("function"); function.Exists() {
|
||||
addIfNotEmpty(segments, function.Get("name").String())
|
||||
addIfNotEmpty(segments, function.Get("description").String())
|
||||
if params := function.Get("parameters"); params.Exists() {
|
||||
addIfNotEmpty(segments, params.Raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addIfNotEmpty(segments *[]string, value string) {
|
||||
if segments == nil {
|
||||
return
|
||||
}
|
||||
if trimmed := strings.TrimSpace(value); trimmed != "" {
|
||||
*segments = append(*segments, trimmed)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user