完善文档页文本向量与重排序调用支持
This commit is contained in:
@@ -159,16 +159,14 @@ func hasRules(policy map[string]any) bool {
|
||||
}
|
||||
|
||||
func estimateRequestTokens(body map[string]any) int {
|
||||
text := ""
|
||||
if prompt := stringFromMap(body, "prompt"); prompt != "" {
|
||||
text += prompt
|
||||
}
|
||||
if input := stringFromMap(body, "input"); input != "" {
|
||||
text += input
|
||||
}
|
||||
var text strings.Builder
|
||||
appendTokenEstimateText(&text, body["prompt"])
|
||||
appendTokenEstimateText(&text, body["input"])
|
||||
appendTokenEstimateText(&text, body["query"])
|
||||
appendTokenEstimateText(&text, body["documents"])
|
||||
for _, item := range contentItems(body["content"]) {
|
||||
if stringFromAny(item["type"]) == "text" {
|
||||
text += stringFromAny(item["text"])
|
||||
appendTokenEstimateText(&text, item["text"])
|
||||
}
|
||||
}
|
||||
if messages, ok := body["messages"].([]any); ok {
|
||||
@@ -176,19 +174,41 @@ func estimateRequestTokens(body map[string]any) int {
|
||||
message, _ := raw.(map[string]any)
|
||||
switch content := message["content"].(type) {
|
||||
case string:
|
||||
text += content
|
||||
appendTokenEstimateText(&text, content)
|
||||
case []any:
|
||||
for _, rawPart := range content {
|
||||
part, _ := rawPart.(map[string]any)
|
||||
text += stringFromMap(part, "text")
|
||||
appendTokenEstimateText(&text, part["text"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if text == "" {
|
||||
estimatedText := text.String()
|
||||
if estimatedText == "" {
|
||||
return 1
|
||||
}
|
||||
return len([]rune(text))/4 + 1
|
||||
return len([]rune(estimatedText))/4 + 1
|
||||
}
|
||||
|
||||
func appendTokenEstimateText(out *strings.Builder, value any) {
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
out.WriteString(typed)
|
||||
case []string:
|
||||
for _, item := range typed {
|
||||
out.WriteString(item)
|
||||
}
|
||||
case []any:
|
||||
for _, item := range typed {
|
||||
appendTokenEstimateText(out, item)
|
||||
}
|
||||
case map[string]any:
|
||||
for _, key := range []string{"text", "content", "query", "document"} {
|
||||
if text := stringFromAny(typed[key]); text != "" {
|
||||
out.WriteString(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tokenUsageAmounts(usage clients.Usage) map[string]float64 {
|
||||
|
||||
@@ -40,8 +40,11 @@ func (s *Service) Estimate(ctx context.Context, kind string, model string, body
|
||||
}
|
||||
|
||||
func (s *Service) estimatedBillings(ctx context.Context, user *auth.User, kind string, body map[string]any, candidate store.RuntimeModelCandidate) []any {
|
||||
usage := clients.Usage{InputTokens: estimateRequestTokens(body), OutputTokens: int(floatFromAny(body["max_tokens"]))}
|
||||
if usage.OutputTokens == 0 {
|
||||
usage := clients.Usage{InputTokens: estimateRequestTokens(body)}
|
||||
if isTextGenerationKind(kind) {
|
||||
usage.OutputTokens = int(floatFromAny(body["max_tokens"]))
|
||||
}
|
||||
if isTextGenerationKind(kind) && usage.OutputTokens == 0 {
|
||||
usage.OutputTokens = 64
|
||||
}
|
||||
usage.TotalTokens = usage.InputTokens + usage.OutputTokens
|
||||
@@ -56,19 +59,25 @@ func (s *Service) estimatedBillings(ctx context.Context, user *auth.User, kind s
|
||||
func (s *Service) billings(ctx context.Context, user *auth.User, kind string, body map[string]any, candidate store.RuntimeModelCandidate, response clients.Response, simulated bool) []any {
|
||||
config := s.effectiveBillingConfig(ctx, candidate)
|
||||
discount := effectiveDiscount(ctx, s.store, user, candidate)
|
||||
if isTextGenerationKind(kind) {
|
||||
if isTextBillingKind(kind) {
|
||||
inputTokens := response.Usage.InputTokens
|
||||
outputTokens := response.Usage.OutputTokens
|
||||
if isTextInputOnlyKind(kind) && inputTokens == 0 && response.Usage.TotalTokens > 0 {
|
||||
inputTokens = response.Usage.TotalTokens
|
||||
}
|
||||
if inputTokens == 0 && outputTokens == 0 {
|
||||
inputTokens = estimateRequestTokens(body)
|
||||
outputTokens = 1
|
||||
if isTextGenerationKind(kind) {
|
||||
outputTokens = 1
|
||||
}
|
||||
}
|
||||
inputAmount := roundPrice(float64(inputTokens) / 1000 * resourcePrice(config, "text", "textInputPer1k", "inputTokenPrice", "basePrice") * discount)
|
||||
outputAmount := roundPrice(float64(outputTokens) / 1000 * resourcePrice(config, "text", "textOutputPer1k", "outputTokenPrice", "basePrice") * discount)
|
||||
return []any{
|
||||
billingLine(candidate, "text_input", "1k_tokens", inputTokens, inputAmount, discount, simulated),
|
||||
billingLine(candidate, "text_output", "1k_tokens", outputTokens, outputAmount, discount, simulated),
|
||||
lines := []any{billingLine(candidate, "text_input", "1k_tokens", inputTokens, inputAmount, discount, simulated)}
|
||||
if isTextGenerationKind(kind) {
|
||||
outputAmount := roundPrice(float64(outputTokens) / 1000 * resourcePrice(config, "text", "textOutputPer1k", "outputTokenPrice", "basePrice") * discount)
|
||||
lines = append(lines, billingLine(candidate, "text_output", "1k_tokens", outputTokens, outputAmount, discount, simulated))
|
||||
}
|
||||
return lines
|
||||
}
|
||||
count := requestOutputCount(body)
|
||||
resource := "image"
|
||||
|
||||
@@ -698,7 +698,7 @@ func (s *Service) recordTaskParameterPreprocessing(ctx context.Context, taskID s
|
||||
|
||||
func skipTaskParameterPreprocessingLog(modelType string) bool {
|
||||
switch strings.TrimSpace(modelType) {
|
||||
case "text_generate", "chat", "responses", "text":
|
||||
case "text_generate", "text_embedding", "text_rerank", "chat", "responses", "text":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -923,6 +923,10 @@ func modelTypeFromKind(kind string, body map[string]any) string {
|
||||
switch kind {
|
||||
case "chat.completions", "responses":
|
||||
return "text_generate"
|
||||
case "embeddings":
|
||||
return "text_embedding"
|
||||
case "reranks":
|
||||
return "text_rerank"
|
||||
case "images.generations", "images.edits":
|
||||
if kind == "images.edits" {
|
||||
return "image_edit"
|
||||
@@ -943,7 +947,7 @@ func modelTypeFromKind(kind string, body map[string]any) string {
|
||||
|
||||
func requestedModelTypeFromBody(body map[string]any) string {
|
||||
for _, key := range []string{"modelType", "model_type", "capability", "capabilityType"} {
|
||||
value := strings.TrimSpace(stringFromMap(body, key))
|
||||
value := canonicalModelType(strings.TrimSpace(stringFromMap(body, key)))
|
||||
if isKnownModelType(value) {
|
||||
return value
|
||||
}
|
||||
@@ -951,9 +955,21 @@ func requestedModelTypeFromBody(body map[string]any) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func canonicalModelType(value string) string {
|
||||
normalized := strings.ReplaceAll(strings.ToLower(strings.TrimSpace(value)), "-", "_")
|
||||
switch normalized {
|
||||
case "embedding":
|
||||
return "text_embedding"
|
||||
case "rerank", "reranks":
|
||||
return "text_rerank"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
func isKnownModelType(value string) bool {
|
||||
switch value {
|
||||
case "text_generate", "image_generate", "image_edit", "video_generate", "image_to_video", "text_to_video", "video_edit", "video_reference", "video_first_last_frame", "omni_video", "omni":
|
||||
case "text_generate", "text_embedding", "text_rerank", "image_generate", "image_edit", "video_generate", "image_to_video", "text_to_video", "video_edit", "video_reference", "video_first_last_frame", "omni_video", "omni":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -1001,6 +1017,14 @@ func isTextGenerationKind(kind string) bool {
|
||||
return kind == "chat.completions" || kind == "responses"
|
||||
}
|
||||
|
||||
func isTextInputOnlyKind(kind string) bool {
|
||||
return kind == "embeddings" || kind == "reranks"
|
||||
}
|
||||
|
||||
func isTextBillingKind(kind string) bool {
|
||||
return isTextGenerationKind(kind) || isTextInputOnlyKind(kind)
|
||||
}
|
||||
|
||||
func isSimulation(task store.GatewayTask, candidate store.RuntimeModelCandidate) bool {
|
||||
if task.RunMode == "simulation" {
|
||||
return true
|
||||
@@ -1115,6 +1139,17 @@ func validateRequest(kind string, body map[string]any) error {
|
||||
if body["input"] == nil && body["messages"] == nil {
|
||||
return errors.New("input or messages is required")
|
||||
}
|
||||
case "embeddings":
|
||||
if body["input"] == nil {
|
||||
return errors.New("input is required")
|
||||
}
|
||||
case "reranks":
|
||||
if body["query"] == nil {
|
||||
return errors.New("query is required")
|
||||
}
|
||||
if !hasRerankDocuments(body["documents"]) {
|
||||
return errors.New("documents is required")
|
||||
}
|
||||
case "images.generations", "images.edits":
|
||||
if strings.TrimSpace(stringFromMap(body, "prompt")) == "" {
|
||||
return errors.New("prompt is required")
|
||||
@@ -1123,6 +1158,17 @@ func validateRequest(kind string, body map[string]any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasRerankDocuments(value any) bool {
|
||||
switch typed := value.(type) {
|
||||
case []any:
|
||||
return len(typed) > 0
|
||||
case []string:
|
||||
return len(typed) > 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func parameterPreprocessClientError(err error) *clients.ClientError {
|
||||
if err == nil {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user