2026-07-15 14:59:23 +02:00
//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package models
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
2026-07-18 21:02:07 +08:00
"ragflow/internal/common"
2026-07-15 14:59:23 +02:00
"strings"
)
// RAGconModel implements ModelDriver for RAGcon (https://connect.ragcon.com),
// a LiteLLM-proxy gateway exposing OpenAI-compatible endpoints for chat,
// embedding, rerank, ASR and TTS. Model names are user-supplied (no fixed
// catalog), matching the Ollama/Xinference-style providers.
type RAGconModel struct {
baseModel BaseModel
}
// NewRAGconModel creates a new RAGcon model instance.
func NewRAGconModel ( baseURL map [ string ] string , urlSuffix URLSuffix ) * RAGconModel {
return & RAGconModel {
baseModel : BaseModel {
BaseURL : baseURL ,
URLSuffix : urlSuffix ,
httpClient : NewDriverHTTPClient ( ) ,
} ,
}
}
func ( r * RAGconModel ) NewInstance ( baseURL map [ string ] string ) ModelDriver {
return NewRAGconModel ( baseURL , r . baseModel . URLSuffix )
}
func ( r * RAGconModel ) Name ( ) string {
return "ragcon"
}
func ( r * RAGconModel ) chatURL ( apiConfig * APIConfig ) ( string , error ) {
baseURL , err := r . baseModel . GetBaseURL ( apiConfig )
if err != nil {
return "" , err
}
baseURL = strings . TrimSuffix ( baseURL , "/" )
return fmt . Sprintf ( "%s/%s" , baseURL , r . baseModel . URLSuffix . Chat ) , nil
}
func ragconChatMessages ( messages [ ] Message ) [ ] map [ string ] interface { } {
apiMessages := make ( [ ] map [ string ] interface { } , len ( messages ) )
for i , msg := range messages {
apiMsg := map [ string ] interface { } {
"role" : msg . Role ,
"content" : msg . Content ,
}
if msg . ToolCallID != "" {
apiMsg [ "tool_call_id" ] = msg . ToolCallID
}
if len ( msg . ToolCalls ) > 0 {
apiMsg [ "tool_calls" ] = msg . ToolCalls
}
apiMessages [ i ] = apiMsg
}
return apiMessages
}
func ragconChatPayload ( modelName string , messages [ ] Message , stream bool , chatModelConfig * ChatConfig ) map [ string ] interface { } {
reqBody := map [ string ] interface { } {
"model" : modelName ,
"messages" : ragconChatMessages ( messages ) ,
"stream" : stream ,
}
if stream {
reqBody [ "stream_options" ] = map [ string ] interface { } { "include_usage" : true }
}
if chatModelConfig != nil {
if chatModelConfig . MaxTokens != nil {
reqBody [ "max_tokens" ] = * chatModelConfig . MaxTokens
}
if chatModelConfig . Temperature != nil {
reqBody [ "temperature" ] = * chatModelConfig . Temperature
}
if chatModelConfig . TopP != nil {
reqBody [ "top_p" ] = * chatModelConfig . TopP
}
if chatModelConfig . Stop != nil {
reqBody [ "stop" ] = * chatModelConfig . Stop
}
if chatModelConfig . Tools != nil {
reqBody [ "tools" ] = chatModelConfig . Tools
tc := "auto"
if chatModelConfig . ToolChoice != nil {
tc = * chatModelConfig . ToolChoice
}
reqBody [ "tool_choice" ] = tc
}
}
// Qwen3 family: disable thinking by default (mirrors the same policy
// applied by the other OpenAI-compatible Go drivers).
if strings . Contains ( strings . ToLower ( modelName ) , "qwen3" ) && ( chatModelConfig == nil || chatModelConfig . Thinking == nil ) {
reqBody [ "enable_thinking" ] = false
}
return reqBody
}
2026-07-18 21:02:07 +08:00
func ( r * RAGconModel ) ChatWithMessages ( modelName string , messages [ ] Message , apiConfig * APIConfig , chatModelConfig * ChatConfig , modelUsage * common . ModelUsage ) ( * ChatResponse , error ) {
2026-07-15 14:59:23 +02:00
if err := r . baseModel . APIConfigCheck ( apiConfig ) ; err != nil {
return nil , err
}
if strings . TrimSpace ( modelName ) == "" {
return nil , fmt . Errorf ( "model name is required" )
}
if len ( messages ) == 0 {
return nil , fmt . Errorf ( "messages is empty" )
}
url , err := r . chatURL ( apiConfig )
if err != nil {
return nil , err
}
jsonData , err := json . Marshal ( ragconChatPayload ( modelName , messages , false , chatModelConfig ) )
if err != nil {
return nil , fmt . Errorf ( "failed to marshal request: %w" , err )
}
ctx , cancel := context . WithTimeout ( context . Background ( ) , nonStreamCallTimeout )
defer cancel ( )
req , err := http . NewRequestWithContext ( ctx , http . MethodPost , url , bytes . NewBuffer ( jsonData ) )
if err != nil {
return nil , fmt . Errorf ( "failed to create request: %w" , err )
}
req . Header . Set ( "Content-Type" , "application/json" )
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , * apiConfig . ApiKey ) )
resp , err := r . baseModel . httpClient . Do ( req )
if err != nil {
return nil , fmt . Errorf ( "failed to send request: %w" , err )
}
defer resp . Body . Close ( )
body , err := io . ReadAll ( resp . Body )
if err != nil {
return nil , fmt . Errorf ( "failed to read response: %w" , err )
}
if resp . StatusCode != http . StatusOK {
return nil , fmt . Errorf ( "RAGcon API request failed with status %d: %s" , resp . StatusCode , string ( body ) )
}
var result map [ string ] interface { }
if err = json . Unmarshal ( body , & result ) ; err != nil {
return nil , fmt . Errorf ( "failed to parse response: %w" , err )
}
choices , ok := result [ "choices" ] . ( [ ] interface { } )
if ! ok || len ( choices ) == 0 {
return nil , fmt . Errorf ( "no choices in response" )
}
firstChoice , ok := choices [ 0 ] . ( map [ string ] interface { } )
if ! ok {
return nil , fmt . Errorf ( "invalid choice format" )
}
messageMap , ok := firstChoice [ "message" ] . ( map [ string ] interface { } )
if ! ok {
return nil , fmt . Errorf ( "invalid message format" )
}
var content string
if c , ok := messageMap [ "content" ] . ( string ) ; ok {
content = c
}
var reasonContent string
if rc , ok := messageMap [ "reasoning_content" ] . ( string ) ; ok {
reasonContent = rc
} else if rc , ok := messageMap [ "reasoning" ] . ( string ) ; ok {
reasonContent = rc
}
var toolCalls [ ] map [ string ] interface { }
if tcs , ok := messageMap [ "tool_calls" ] . ( [ ] interface { } ) ; ok {
for _ , tc := range tcs {
if tcMap , ok := tc . ( map [ string ] interface { } ) ; ok {
toolCalls = append ( toolCalls , tcMap )
}
}
}
chatResponse := & ChatResponse {
Answer : & content ,
ReasonContent : & reasonContent ,
ToolCalls : toolCalls ,
}
if pt , ct , tt := extractUsageFromMap ( result ) ; tt > 0 {
2026-07-18 21:02:07 +08:00
chatResponse . Usage = & TokenUsage { PromptTokens : pt , CompletionTokens : ct , TotalTokens : tt }
2026-07-15 14:59:23 +02:00
}
return chatResponse , nil
}
2026-07-18 21:02:07 +08:00
func ( r * RAGconModel ) ChatStreamlyWithSender ( modelName string , messages [ ] Message , apiConfig * APIConfig , chatModelConfig * ChatConfig , modelUsage * common . ModelUsage , sender func ( * string , * string ) error ) error {
2026-07-15 14:59:23 +02:00
if err := r . baseModel . APIConfigCheck ( apiConfig ) ; err != nil {
return err
}
if sender == nil {
return fmt . Errorf ( "sender is required" )
}
if strings . TrimSpace ( modelName ) == "" {
return fmt . Errorf ( "model name is required" )
}
if len ( messages ) == 0 {
return fmt . Errorf ( "messages is empty" )
}
if chatModelConfig != nil && chatModelConfig . Stream != nil && ! * chatModelConfig . Stream {
return fmt . Errorf ( "stream must be true in ChatStreamlyWithSender" )
}
url , err := r . chatURL ( apiConfig )
if err != nil {
return err
}
jsonData , err := json . Marshal ( ragconChatPayload ( modelName , messages , true , chatModelConfig ) )
if err != nil {
return fmt . Errorf ( "failed to marshal request: %w" , err )
}
req , err := http . NewRequestWithContext ( context . Background ( ) , http . MethodPost , url , bytes . NewBuffer ( jsonData ) )
if err != nil {
return fmt . Errorf ( "failed to create request: %w" , err )
}
req . Header . Set ( "Content-Type" , "application/json" )
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , * apiConfig . ApiKey ) )
resp , err := r . baseModel . httpClient . Do ( req )
if err != nil {
return fmt . Errorf ( "failed to send request: %w" , err )
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
body , _ := io . ReadAll ( resp . Body )
return fmt . Errorf ( "RAGcon API request failed with status %d: %s" , resp . StatusCode , string ( body ) )
}
sawTerminal := false
accumulatedToolCalls := make ( map [ int ] map [ string ] interface { } )
2026-07-18 21:02:07 +08:00
var streamUsage * TokenUsage
2026-07-15 14:59:23 +02:00
scanner := bufio . NewScanner ( resp . Body )
scanner . Buffer ( make ( [ ] byte , 64 * 1024 ) , 1024 * 1024 )
for scanner . Scan ( ) {
line := scanner . Text ( )
if ! strings . HasPrefix ( line , "data:" ) {
continue
}
data := strings . TrimSpace ( line [ 5 : ] )
if data == "" {
continue
}
if data == "[DONE]" {
sawTerminal = true
break
}
var event map [ string ] interface { }
if err = json . Unmarshal ( [ ] byte ( data ) , & event ) ; err != nil {
continue
}
if pt , ct , tt := extractUsageFromMap ( event ) ; tt > 0 {
2026-07-18 21:02:07 +08:00
streamUsage = & TokenUsage { PromptTokens : pt , CompletionTokens : ct , TotalTokens : tt }
2026-07-15 14:59:23 +02:00
}
choices , ok := event [ "choices" ] . ( [ ] interface { } )
if ! ok || len ( choices ) == 0 {
continue
}
firstChoice , ok := choices [ 0 ] . ( map [ string ] interface { } )
if ! ok {
continue
}
delta , ok := firstChoice [ "delta" ] . ( map [ string ] interface { } )
if ! ok {
continue
}
if tcs , ok := delta [ "tool_calls" ] . ( [ ] interface { } ) ; ok {
for _ , tc := range tcs {
tcMap , ok := tc . ( map [ string ] interface { } )
if ! ok {
continue
}
idxF , ok := tcMap [ "index" ] . ( float64 )
if ! ok {
continue
}
idx := int ( idxF )
if existing , hasExisting := accumulatedToolCalls [ idx ] ; hasExisting {
if fn , ok := tcMap [ "function" ] . ( map [ string ] interface { } ) ; ok {
if args , ok := fn [ "arguments" ] . ( string ) ; ok {
if ef , ok := existing [ "function" ] . ( map [ string ] interface { } ) ; ok {
if ea , ok := ef [ "arguments" ] . ( string ) ; ok {
ef [ "arguments" ] = ea + args
} else {
ef [ "arguments" ] = args
}
}
}
}
} else {
accumulatedToolCalls [ idx ] = cloneMap ( tcMap )
}
}
continue
}
if reasoningContent , ok := delta [ "reasoning_content" ] . ( string ) ; ok && reasoningContent != "" {
if err := sender ( nil , & reasoningContent ) ; err != nil {
return err
}
}
if content , ok := delta [ "content" ] . ( string ) ; ok && content != "" {
if err := sender ( & content , nil ) ; err != nil {
return err
}
}
if finishReason , ok := firstChoice [ "finish_reason" ] . ( string ) ; ok && finishReason != "" {
sawTerminal = true
}
}
if err := scanner . Err ( ) ; err != nil {
return fmt . Errorf ( "failed to scan response body: %w" , err )
}
if ! sawTerminal {
return fmt . Errorf ( "ragcon: stream ended before [DONE] or finish_reason" )
}
if len ( accumulatedToolCalls ) > 0 && chatModelConfig != nil {
tcs := make ( [ ] map [ string ] interface { } , 0 , len ( accumulatedToolCalls ) )
for _ , tc := range accumulatedToolCalls {
tcs = append ( tcs , tc )
}
chatModelConfig . ToolCallsResult = & tcs
}
if streamUsage != nil && chatModelConfig != nil {
chatModelConfig . UsageResult = streamUsage
}
endOfStream := "[DONE]"
return sender ( & endOfStream , nil )
}
type ragconEmbeddingResponse struct {
Data [ ] struct {
Embedding [ ] float64 ` json:"embedding" `
Index int ` json:"index" `
} ` json:"data" `
}
2026-07-18 21:02:07 +08:00
func ( r * RAGconModel ) Embed ( modelName * string , texts [ ] string , apiConfig * APIConfig , embeddingConfig * EmbeddingConfig , modelUsage * common . ModelUsage ) ( [ ] EmbeddingData , error ) {
2026-07-15 14:59:23 +02:00
if err := r . baseModel . APIConfigCheck ( apiConfig ) ; err != nil {
return nil , err
}
if modelName == nil || strings . TrimSpace ( * modelName ) == "" {
return nil , fmt . Errorf ( "model name is required" )
}
if len ( texts ) == 0 {
return [ ] EmbeddingData { } , nil
}
baseURL , err := r . baseModel . GetBaseURL ( apiConfig )
if err != nil {
return nil , err
}
baseURL = strings . TrimSuffix ( baseURL , "/" )
url := fmt . Sprintf ( "%s/%s" , baseURL , r . baseModel . URLSuffix . Embedding )
reqBody := map [ string ] interface { } {
"model" : * modelName ,
"input" : texts ,
}
if embeddingConfig != nil && embeddingConfig . Dimension > 0 {
reqBody [ "dimensions" ] = embeddingConfig . Dimension
}
jsonData , err := json . Marshal ( reqBody )
if err != nil {
return nil , fmt . Errorf ( "failed to marshal request: %w" , err )
}
ctx , cancel := context . WithTimeout ( context . Background ( ) , nonStreamCallTimeout )
defer cancel ( )
req , err := http . NewRequestWithContext ( ctx , http . MethodPost , url , bytes . NewBuffer ( jsonData ) )
if err != nil {
return nil , fmt . Errorf ( "failed to create request: %w" , err )
}
req . Header . Set ( "Content-Type" , "application/json" )
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , * apiConfig . ApiKey ) )
resp , err := r . baseModel . httpClient . Do ( req )
if err != nil {
return nil , fmt . Errorf ( "failed to send request: %w" , err )
}
defer resp . Body . Close ( )
body , err := io . ReadAll ( resp . Body )
if err != nil {
return nil , fmt . Errorf ( "failed to read response: %w" , err )
}
if resp . StatusCode != http . StatusOK {
return nil , fmt . Errorf ( "RAGcon embeddings API error: %s, body: %s" , resp . Status , string ( body ) )
}
var parsed ragconEmbeddingResponse
if err = json . Unmarshal ( body , & parsed ) ; err != nil {
return nil , fmt . Errorf ( "failed to parse response: %w" , err )
}
embeddings := make ( [ ] EmbeddingData , 0 , len ( parsed . Data ) )
for _ , d := range parsed . Data {
embeddings = append ( embeddings , EmbeddingData { Embedding : d . Embedding , Index : d . Index } )
}
return embeddings , nil
}
// Rerank POSTs to RAGcon's /rerank endpoint (LiteLLM proxy passthrough).
2026-07-18 21:02:07 +08:00
func ( r * RAGconModel ) Rerank ( modelName * string , query string , documents [ ] string , apiConfig * APIConfig , rerankConfig * RerankConfig , modelUsage * common . ModelUsage ) ( * RerankResponse , error ) {
2026-07-15 14:59:23 +02:00
if err := r . baseModel . APIConfigCheck ( apiConfig ) ; err != nil {
return nil , err
}
if modelName == nil || strings . TrimSpace ( * modelName ) == "" {
return nil , fmt . Errorf ( "model name is required" )
}
if len ( documents ) == 0 {
return & RerankResponse { } , nil
}
baseURL , err := r . baseModel . GetBaseURL ( apiConfig )
if err != nil {
return nil , err
}
baseURL = strings . TrimSuffix ( baseURL , "/" )
url := fmt . Sprintf ( "%s/%s" , baseURL , r . baseModel . URLSuffix . Rerank )
topN := len ( documents )
if rerankConfig != nil && rerankConfig . TopN != 0 {
topN = rerankConfig . TopN
}
reqBody := map [ string ] interface { } {
"model" : * modelName ,
"query" : query ,
"documents" : documents ,
"top_n" : topN ,
}
jsonData , err := json . Marshal ( reqBody )
if err != nil {
return nil , fmt . Errorf ( "failed to marshal request: %w" , err )
}
ctx , cancel := context . WithTimeout ( context . Background ( ) , nonStreamCallTimeout )
defer cancel ( )
req , err := http . NewRequestWithContext ( ctx , http . MethodPost , url , bytes . NewBuffer ( jsonData ) )
if err != nil {
return nil , fmt . Errorf ( "failed to create request: %w" , err )
}
req . Header . Set ( "Content-Type" , "application/json" )
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , * apiConfig . ApiKey ) )
resp , err := r . baseModel . httpClient . Do ( req )
if err != nil {
return nil , fmt . Errorf ( "failed to send request: %w" , err )
}
defer resp . Body . Close ( )
body , err := io . ReadAll ( resp . Body )
if err != nil {
return nil , fmt . Errorf ( "failed to read response: %w" , err )
}
if resp . StatusCode != http . StatusOK {
return nil , fmt . Errorf ( "RAGcon rerank API error: status %d, body: %s" , resp . StatusCode , string ( body ) )
}
var rerankResp struct {
Results [ ] struct {
Index int ` json:"index" `
RelevanceScore float64 ` json:"relevance_score" `
} ` json:"results" `
}
if err = json . Unmarshal ( body , & rerankResp ) ; err != nil {
return nil , fmt . Errorf ( "failed to decode response: %w" , err )
}
var rerankResponse RerankResponse
for _ , result := range rerankResp . Results {
rerankResponse . Data = append ( rerankResponse . Data , RerankResult {
Index : result . Index ,
RelevanceScore : result . RelevanceScore ,
} )
}
return & rerankResponse , nil
}
// ListModels returns the model ids visible to the configured API key.
func ( r * RAGconModel ) ListModels ( apiConfig * APIConfig ) ( [ ] ListModelResponse , error ) {
if err := r . baseModel . APIConfigCheck ( apiConfig ) ; err != nil {
return nil , err
}
baseURL , err := r . baseModel . GetBaseURL ( apiConfig )
if err != nil {
return nil , err
}
baseURL = strings . TrimSuffix ( baseURL , "/" )
url := fmt . Sprintf ( "%s/%s" , baseURL , r . baseModel . URLSuffix . Models )
ctx , cancel := context . WithTimeout ( context . Background ( ) , nonStreamCallTimeout )
defer cancel ( )
req , err := http . NewRequestWithContext ( ctx , http . MethodGet , url , nil )
if err != nil {
return nil , fmt . Errorf ( "failed to create request: %w" , err )
}
req . Header . Set ( "Content-Type" , "application/json" )
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , * apiConfig . ApiKey ) )
resp , err := r . baseModel . httpClient . Do ( req )
if err != nil {
return nil , fmt . Errorf ( "failed to send request: %w" , err )
}
defer resp . Body . Close ( )
body , err := io . ReadAll ( resp . Body )
if err != nil {
return nil , fmt . Errorf ( "failed to read response: %w" , err )
}
if resp . StatusCode != http . StatusOK {
return nil , fmt . Errorf ( "API request failed with status %d: %s" , resp . StatusCode , string ( body ) )
}
var modelList ModelList
if err = json . Unmarshal ( body , & modelList ) ; err != nil {
return nil , fmt . Errorf ( "failed to parse response: %w" , err )
}
return ParseListModel ( modelList ) , nil
}
func ( r * RAGconModel ) CheckConnection ( apiConfig * APIConfig ) error {
_ , err := r . ListModels ( apiConfig )
return err
}
// Balance is not exposed by RAGcon's LiteLLM proxy.
func ( r * RAGconModel ) Balance ( apiConfig * APIConfig ) ( map [ string ] interface { } , error ) {
return nil , fmt . Errorf ( "%s, no such method" , r . Name ( ) )
}
func ( r * RAGconModel ) newASRRequest ( ctx context . Context , modelName * string , file * string , apiConfig * APIConfig , asrConfig * ASRConfig , stream bool ) ( * http . Request , string , error ) {
if err := r . baseModel . APIConfigCheck ( apiConfig ) ; err != nil {
return nil , "" , err
}
if modelName == nil || strings . TrimSpace ( * modelName ) == "" {
return nil , "" , fmt . Errorf ( "model name is required" )
}
if file == nil || * file == "" {
return nil , "" , fmt . Errorf ( "file is missing" )
}
baseURL , err := r . baseModel . GetBaseURL ( apiConfig )
if err != nil {
return nil , "" , err
}
baseURL = strings . TrimSuffix ( baseURL , "/" )
url := fmt . Sprintf ( "%s/%s" , baseURL , strings . TrimPrefix ( r . baseModel . URLSuffix . ASR , "/" ) )
var body bytes . Buffer
writer := multipart . NewWriter ( & body )
// codeql[go/path-injection] False positive: *file is the audio file path the caller passes in to upload. The caller (an operator-supplied pipeline) explicitly chose this path, and the OS access check enforces permissions anyway.
audioFile , err := os . Open ( * file )
if err != nil {
return nil , "" , fmt . Errorf ( "failed to open audio file: %w" , err )
}
defer audioFile . Close ( )
part , err := writer . CreateFormFile ( "file" , filepath . Base ( * file ) )
if err != nil {
return nil , "" , fmt . Errorf ( "failed to create multipart file: %w" , err )
}
if _ , err = io . Copy ( part , audioFile ) ; err != nil {
return nil , "" , fmt . Errorf ( "failed to copy audio data: %w" , err )
}
if err = writer . WriteField ( "model" , * modelName ) ; err != nil {
return nil , "" , fmt . Errorf ( "failed to write model field: %w" , err )
}
responseFormat := ""
if asrConfig != nil && asrConfig . Params != nil {
if value , ok := asrConfig . Params [ "response_format" ] . ( string ) ; ok {
responseFormat = value
}
for key , value := range asrConfig . Params {
if stream && key == "stream" {
continue
}
if err = writeOpenAIMultipartField ( writer , key , value ) ; err != nil {
return nil , "" , err
}
}
}
if stream {
if err = writer . WriteField ( "stream" , "true" ) ; err != nil {
return nil , "" , fmt . Errorf ( "failed to write stream field: %w" , err )
}
}
if err = writer . Close ( ) ; err != nil {
return nil , "" , fmt . Errorf ( "failed to close multipart writer: %w" , err )
}
req , err := http . NewRequestWithContext ( ctx , http . MethodPost , url , & body )
if err != nil {
return nil , "" , fmt . Errorf ( "failed to create request: %w" , err )
}
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , * apiConfig . ApiKey ) )
req . Header . Set ( "Content-Type" , writer . FormDataContentType ( ) )
return req , responseFormat , nil
}
2026-07-18 21:02:07 +08:00
func ( r * RAGconModel ) TranscribeAudio ( modelName * string , file * string , apiConfig * APIConfig , asrConfig * ASRConfig , modelUsage * common . ModelUsage ) ( * ASRResponse , error ) {
2026-07-15 14:59:23 +02:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , nonStreamCallTimeout )
defer cancel ( )
req , responseFormat , err := r . newASRRequest ( ctx , modelName , file , apiConfig , asrConfig , false )
if err != nil {
return nil , err
}
req . Header . Set ( "Accept" , "application/json" )
resp , err := r . baseModel . httpClient . Do ( req )
if err != nil {
return nil , fmt . Errorf ( "failed to send request: %w" , err )
}
defer resp . Body . Close ( )
respBody , err := io . ReadAll ( resp . Body )
if err != nil {
return nil , fmt . Errorf ( "failed to read response body: %w" , err )
}
if resp . StatusCode != http . StatusOK {
return nil , fmt . Errorf ( "RAGcon ASR API error: %s, body: %s" , resp . Status , string ( respBody ) )
}
return decodeOpenAIASRResponse ( respBody , responseFormat )
}
2026-07-18 21:02:07 +08:00
func ( r * RAGconModel ) TranscribeAudioWithSender ( modelName * string , file * string , apiConfig * APIConfig , asrConfig * ASRConfig , modelUsage * common . ModelUsage , sender func ( * string , * string ) error ) error {
2026-07-15 14:59:23 +02:00
if sender == nil {
return fmt . Errorf ( "sender is required" )
}
req , responseFormat , err := r . newASRRequest ( context . Background ( ) , modelName , file , apiConfig , asrConfig , true )
if err != nil {
return err
}
req . Header . Set ( "Accept" , "text/event-stream" )
resp , err := r . baseModel . httpClient . Do ( req )
if err != nil {
return fmt . Errorf ( "failed to send request: %w" , err )
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
respBody , _ := io . ReadAll ( resp . Body )
return fmt . Errorf ( "RAGcon ASR stream API error: %s, body: %s" , resp . Status , string ( respBody ) )
}
if ! strings . Contains ( resp . Header . Get ( "Content-Type" ) , "text/event-stream" ) {
respBody , err := io . ReadAll ( resp . Body )
if err != nil {
return fmt . Errorf ( "failed to read response body: %w" , err )
}
response , err := decodeOpenAIASRResponse ( respBody , responseFormat )
if err != nil {
return err
}
if response != nil && response . Text != "" {
if err = sender ( & response . Text , nil ) ; err != nil {
return err
}
}
done := "[DONE]"
return sender ( & done , nil )
}
sentDelta := false
if _ , err = ParseSSEStream [ struct {
Type string ` json:"type" `
Delta string ` json:"delta" `
Text string ` json:"text" `
} ] ( resp . Body , func ( event struct {
Type string ` json:"type" `
Delta string ` json:"delta" `
Text string ` json:"text" `
} ) error {
switch {
case event . Delta != "" :
if err = sender ( & event . Delta , nil ) ; err != nil {
return err
}
sentDelta = true
case event . Type == "transcript.text.segment" && event . Text != "" :
if err = sender ( & event . Text , nil ) ; err != nil {
return err
}
sentDelta = true
case event . Type == "transcript.text.done" && ! sentDelta && event . Text != "" :
if err = sender ( & event . Text , nil ) ; err != nil {
return err
}
}
return nil
} ) ; err != nil {
return fmt . Errorf ( "error reading RAGcon ASR stream: %w" , err )
}
done := "[DONE]"
return sender ( & done , nil )
}
func ( r * RAGconModel ) newTTSRequest ( ctx context . Context , modelName * string , audioContent * string , apiConfig * APIConfig , ttsConfig * TTSConfig , stream bool ) ( * http . Request , string , error ) {
if err := r . baseModel . APIConfigCheck ( apiConfig ) ; err != nil {
return nil , "" , err
}
if modelName == nil || strings . TrimSpace ( * modelName ) == "" {
return nil , "" , fmt . Errorf ( "model name is required" )
}
if audioContent == nil || * audioContent == "" {
return nil , "" , fmt . Errorf ( "audio content is empty" )
}
baseURL , err := r . baseModel . GetBaseURL ( apiConfig )
if err != nil {
return nil , "" , err
}
baseURL = strings . TrimSuffix ( baseURL , "/" )
url := fmt . Sprintf ( "%s/%s" , baseURL , strings . TrimPrefix ( r . baseModel . URLSuffix . TTS , "/" ) )
reqBody := map [ string ] interface { } {
"model" : * modelName ,
"input" : * audioContent ,
}
if ttsConfig != nil && ttsConfig . Params != nil {
for key , value := range ttsConfig . Params {
reqBody [ key ] = value
}
}
if ttsConfig != nil && ttsConfig . Format != "" {
reqBody [ "response_format" ] = ttsConfig . Format
}
if stream {
if _ , ok := reqBody [ "stream_format" ] ; ! ok {
reqBody [ "stream_format" ] = "audio"
}
}
voice , ok := reqBody [ "voice" ]
if ! ok || voice == nil {
return nil , "" , fmt . Errorf ( "voice is required" )
}
voiceString , ok := voice . ( string )
if ! ok || strings . TrimSpace ( voiceString ) == "" {
return nil , "" , fmt . Errorf ( "voice is required" )
}
streamFormat , _ := reqBody [ "stream_format" ] . ( string )
jsonData , err := json . Marshal ( reqBody )
if err != nil {
return nil , "" , fmt . Errorf ( "failed to marshal request: %w" , err )
}
req , err := http . NewRequestWithContext ( ctx , http . MethodPost , url , bytes . NewBuffer ( jsonData ) )
if err != nil {
return nil , "" , fmt . Errorf ( "failed to create request: %w" , err )
}
req . Header . Set ( "Content-Type" , "application/json" )
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , * apiConfig . ApiKey ) )
return req , streamFormat , nil
}
2026-07-18 21:02:07 +08:00
func ( r * RAGconModel ) AudioSpeech ( modelName * string , audioContent * string , apiConfig * APIConfig , ttsConfig * TTSConfig , modelUsage * common . ModelUsage ) ( * TTSResponse , error ) {
2026-07-15 14:59:23 +02:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , nonStreamCallTimeout )
defer cancel ( )
req , _ , err := r . newTTSRequest ( ctx , modelName , audioContent , apiConfig , ttsConfig , false )
if err != nil {
return nil , err
}
resp , err := r . baseModel . httpClient . Do ( req )
if err != nil {
return nil , fmt . Errorf ( "failed to send request: %w" , err )
}
defer resp . Body . Close ( )
body , err := io . ReadAll ( resp . Body )
if err != nil {
return nil , fmt . Errorf ( "failed to read response body: %w" , err )
}
if resp . StatusCode != http . StatusOK {
return nil , fmt . Errorf ( "RAGcon TTS API error: %s, body: %s" , resp . Status , string ( body ) )
}
return & TTSResponse { Audio : body } , nil
}
2026-07-18 21:02:07 +08:00
func ( r * RAGconModel ) AudioSpeechWithSender ( modelName * string , audioContent * string , apiConfig * APIConfig , ttsConfig * TTSConfig , modelUsage * common . ModelUsage , sender func ( * string , * string ) error ) error {
2026-07-15 14:59:23 +02:00
if sender == nil {
return fmt . Errorf ( "sender is required" )
}
req , streamFormat , err := r . newTTSRequest ( context . Background ( ) , modelName , audioContent , apiConfig , ttsConfig , true )
if err != nil {
return err
}
if streamFormat == "sse" {
req . Header . Set ( "Accept" , "text/event-stream" )
}
resp , err := r . baseModel . httpClient . Do ( req )
if err != nil {
return fmt . Errorf ( "failed to send request: %w" , err )
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
body , _ := io . ReadAll ( resp . Body )
return fmt . Errorf ( "RAGcon TTS stream API error: %s, body: %s" , resp . Status , string ( body ) )
}
if streamFormat == "sse" || strings . Contains ( resp . Header . Get ( "Content-Type" ) , "text/event-stream" ) {
return readOpenAITTSSSEStream ( resp . Body , sender )
}
return readOpenAITTSRawStream ( resp . Body , sender )
}
// OCRFile is not offered by RAGcon.
2026-07-18 21:02:07 +08:00
func ( r * RAGconModel ) OCRFile ( modelName * string , content [ ] byte , url * string , apiConfig * APIConfig , ocrConfig * OCRConfig , modelUsage * common . ModelUsage ) ( * OCRFileResponse , error ) {
2026-07-15 14:59:23 +02:00
return nil , fmt . Errorf ( "%s, no such method" , r . Name ( ) )
}
// ParseFile is not offered by RAGcon.
2026-07-18 21:02:07 +08:00
func ( r * RAGconModel ) ParseFile ( modelName * string , content [ ] byte , url * string , apiConfig * APIConfig , parseFileConfig * ParseFileConfig , modelUsage * common . ModelUsage ) ( * ParseFileResponse , error ) {
2026-07-15 14:59:23 +02:00
return nil , fmt . Errorf ( "%s, no such method" , r . Name ( ) )
}
func ( r * RAGconModel ) ListTasks ( apiConfig * APIConfig ) ( [ ] ListTaskStatus , error ) {
return nil , fmt . Errorf ( "%s, no such method" , r . Name ( ) )
}
func ( r * RAGconModel ) ShowTask ( taskID string , apiConfig * APIConfig ) ( * TaskResponse , error ) {
return nil , fmt . Errorf ( "%s, no such method" , r . Name ( ) )
}