diff --git a/conf/models/gitee.json b/conf/models/gitee.json index 8940759949..6b1a0732e8 100644 --- a/conf/models/gitee.json +++ b/conf/models/gitee.json @@ -10,7 +10,10 @@ "balance": "tokens/packages/balance", "embedding": "embedding", "rerank": "rerank", - "ocr": "images/ocr" + "ocr": "images/ocr", + "doc_parse": "async/documents/parse", + "tasks": "tasks", + "task": "task" }, "models": [ { @@ -71,6 +74,12 @@ "model_types": [ "ocr" ] + }, + { + "name": "MinerU2.5", + "model_types": [ + "doc_parse" + ] } ] } \ No newline at end of file diff --git a/docs/release_notes.md b/docs/release_notes.md index 293c3910d6..feb974f897 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -36,6 +36,7 @@ Released on May 13, 2026. ### New features +- Data source and parsing: Added column-level semantic/metadata control for the spreadsheet file parser; introduced ETag optimization for incremental synchronization of S3 data sources to avoid unnecessary file transfers. - Enables assigning specific roles like content, metadata, and primary key, to table columns. [#13710](https://github.com/infiniflow/ragflow/pull/13710) ### Improvements diff --git a/internal/cli/client.go b/internal/cli/client.go index 0523b36c05..f28b59464e 100644 --- a/internal/cli/client.go +++ b/internal/cli/client.go @@ -273,6 +273,8 @@ func (c *RAGFlowClient) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { return c.ASRUserCommand(cmd) case "ocr_user_command": return c.OCRUserCommand(cmd) + case "parse_file_user_command": + return c.ParseFileUserCommand(cmd) case "check_provider_connection": return c.CheckProviderConnection(cmd) case "use_model": @@ -285,6 +287,10 @@ func (c *RAGFlowClient) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { return c.ResetDefaultModel(cmd) case "list_user_default_models": return c.ListDefaultModels(cmd) + case "list_tasks_user_command": + return c.ListTasksUserCommand(cmd) + case "show_task_user_command": + return c.ShowTaskUserCommand(cmd) // Dataset, metadata commands case "create_dataset_table": return c.CreateDatasetInDocEngine(cmd) diff --git a/internal/cli/lexer.go b/internal/cli/lexer.go index 6a0d1b0ff3..5f0bf18287 100644 --- a/internal/cli/lexer.go +++ b/internal/cli/lexer.go @@ -437,6 +437,8 @@ func (l *Lexer) lookupIdent(ident string) Token { return Token{Type: TokenRegion, Value: ident} case "URL": return Token{Type: TokenURL, Value: ident} + case "TASK": + return Token{Type: TokenTask, Value: ident} case "TASKS": return Token{Type: TokenTasks, Value: ident} case "LOG": diff --git a/internal/cli/response.go b/internal/cli/response.go index b505a7a53f..ffdd18f1d7 100644 --- a/internal/cli/response.go +++ b/internal/cli/response.go @@ -319,6 +319,44 @@ func (r *EmbeddingsResponse) PrintOut() { } } +type SegmentResponse struct { + Segments []map[string]interface{} `json:"segments"` +} + +type TaskResponse struct { + Code int `json:"code"` + Data map[string]interface{} `json:"data"` + Message string `json:"message"` + Duration float64 + OutputFormat OutputFormat +} + +func (r *TaskResponse) Type() string { + return "task" +} + +func (r *TaskResponse) TimeCost() float64 { + return r.Duration +} + +func (r *TaskResponse) SetOutputFormat(format OutputFormat) { + r.OutputFormat = format +} + +func (r *TaskResponse) PrintOut() { + if r.Code == 0 { + segmentsRaw := r.Data["segments"].([]interface{}) + segments := make([]map[string]interface{}, len(segmentsRaw)) + for i, v := range segmentsRaw { + segments[i] = v.(map[string]interface{}) + } + PrintTableSimpleByFormat(segments, r.OutputFormat) + } else { + fmt.Println("ERROR") + fmt.Printf("%d, %s\n", r.Code, r.Message) + } +} + // ==================== ContextEngine Commands ==================== // ContextListResponse represents the response for ls command diff --git a/internal/cli/types.go b/internal/cli/types.go index 9dd32f55c7..bbcf09a432 100644 --- a/internal/cli/types.go +++ b/internal/cli/types.go @@ -152,6 +152,7 @@ const ( TokenTag TokenRegion TokenURL + TokenTask TokenTasks TokenLog TokenLevel diff --git a/internal/cli/user_command.go b/internal/cli/user_command.go index f631a25275..e99912a400 100644 --- a/internal/cli/user_command.go +++ b/internal/cli/user_command.go @@ -2284,6 +2284,179 @@ func (c *RAGFlowClient) OCRUserCommand(cmd *Command) (ResponseIf, error) { return &result, nil } +func (c *RAGFlowClient) ParseFileUserCommand(cmd *Command) (ResponseIf, error) { + if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { + return nil, fmt.Errorf("API token not set. Please login first") + } + + if c.ServerType != "user" { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + var providerName, instanceName, modelName string + + // Check if composite_model_name is provided in command + if compositeModelName, ok := cmd.Params["composite_model_name"].(string); ok && compositeModelName != "" { + names := strings.Split(compositeModelName, "@") + if len(names) != 3 { + return nil, fmt.Errorf("model name must be in format 'model@instance@provider'") + } + providerName = names[2] + instanceName = names[1] + modelName = names[0] + } else if c.CurrentModel != nil { + // Use current model if set + providerName = c.CurrentModel.Provider + instanceName = c.CurrentModel.Instance + modelName = c.CurrentModel.Model + } else { + return nil, fmt.Errorf("model name not provided and no current model set. Use 'use model' command first") + } + + var filename string + var fileURL string + var ok bool + var fileContent []byte + + filename, ok = cmd.Params["file"].(string) + if ok { + // read file and convert to base64 + var err error + fileContent, err = os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to read file: %w", err) + } + } else { + fileURL, ok = cmd.Params["url"].(string) + if !ok { + return nil, fmt.Errorf("file or url not provided") + } + } + + payload := map[string]interface{}{ + "provider_name": providerName, + "instance_name": instanceName, + "model_name": modelName, + } + + if fileContent != nil { + payload["content"] = fileContent + } else { + payload["url"] = fileURL + } + + url := "/file/parse" + + resp, err := c.HTTPClient.Request("POST", url, "web", nil, payload) + if err != nil { + return nil, fmt.Errorf("failed to PARSE document: %w", err) + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to PARSE document: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + var result CommonDataResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("PARSE document failed: invalid JSON (%w)", err) + } + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + result.Duration = resp.Duration + + return &result, nil +} + +func (c *RAGFlowClient) ListTasksUserCommand(cmd *Command) (ResponseIf, error) { + if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { + return nil, fmt.Errorf("API token not set. Please login first") + } + + if c.ServerType != "user" { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + var providerName, instanceName string + + // Check if composite_instance_name is provided in command + if compositeModelName, ok := cmd.Params["composite_instance_name"].(string); ok && compositeModelName != "" { + names := strings.Split(compositeModelName, "@") + if len(names) != 2 { + return nil, fmt.Errorf("model name must be in format 'instance@provider'") + } + providerName = names[1] + instanceName = names[0] + } else { + return nil, fmt.Errorf("no provider name or instance name") + } + + url := fmt.Sprintf("/providers/%s/instances/%s/tasks", providerName, instanceName) + + resp, err := c.HTTPClient.Request("GET", url, "web", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list tasks: %w", err) + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list tasks: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + var result CommonResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list tasks failed: invalid JSON (%w)", err) + } + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + result.Duration = resp.Duration + return &result, nil +} + +func (c *RAGFlowClient) ShowTaskUserCommand(cmd *Command) (ResponseIf, error) { + if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { + return nil, fmt.Errorf("API token not set. Please login first") + } + + if c.ServerType != "user" { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + var providerName, instanceName string + + // Check if composite_instance_name is provided in command + if compositeModelName, ok := cmd.Params["composite_instance_name"].(string); ok && compositeModelName != "" { + names := strings.Split(compositeModelName, "@") + if len(names) != 2 { + return nil, fmt.Errorf("model name must be in format 'instance@provider'") + } + providerName = names[1] + instanceName = names[0] + } else { + return nil, fmt.Errorf("no provider name or instance name") + } + + taskID, ok := cmd.Params["task_id"].(string) + if !ok { + return nil, fmt.Errorf("task id not provided") + } + + url := fmt.Sprintf("/providers/%s/instances/%s/tasks/%s", providerName, instanceName, taskID) + + resp, err := c.HTTPClient.Request("GET", url, "web", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to get task: %w", err) + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to get task: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + var result TaskResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("get task failed: invalid JSON (%w)", err) + } + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + result.Duration = resp.Duration + return &result, nil +} + func (c *RAGFlowClient) CheckProviderConnection(cmd *Command) (ResponseIf, error) { if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { return nil, fmt.Errorf("API token not set. Please login first") diff --git a/internal/cli/user_parser.go b/internal/cli/user_parser.go index 28f3207114..7b40ee9b2c 100644 --- a/internal/cli/user_parser.go +++ b/internal/cli/user_parser.go @@ -163,6 +163,8 @@ func (p *Parser) parseListCommand() (*Command, error) { return NewCommand("list_user_chats"), nil case TokenFiles: return p.parseListFiles() + case TokenQuotedString: + return p.parseListQuotedStringCommand() default: return nil, fmt.Errorf("unknown LIST target: %s", p.curToken.Value) } @@ -280,9 +282,57 @@ func (p *Parser) parseListFiles() (*Command, error) { return cmd, nil } +func (p *Parser) parseListQuotedStringCommand() (*Command, error) { + str, err := p.parseQuotedString() + if err != nil { + return nil, err + } + p.nextToken() // consume str + switch p.curToken.Type { + case TokenTasks: + p.nextToken() // consume TASKS + cmd := NewCommand("list_tasks_user_command") + cmd.Params["composite_instance_name"] = str + return cmd, nil + default: + return nil, fmt.Errorf("unknown command: %s", str) + } +} + +func (p *Parser) parseShowQuotedStringCommand() (*Command, error) { + str, err := p.parseQuotedString() + if err != nil { + return nil, err + } + p.nextToken() // consume str + switch p.curToken.Type { + case TokenTask: + p.nextToken() // consume TASK + + var taskID string + taskID, err = p.parseQuotedString() + if err != nil { + return nil, fmt.Errorf("expected string: %w", err) + } + p.nextToken() + + cmd := NewCommand("show_task_user_command") + cmd.Params["task_id"] = taskID + cmd.Params["composite_instance_name"] = str + p.nextToken() + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil + default: + return nil, fmt.Errorf("unknown command: %s", str) + } +} + func (p *Parser) parseShowCommand() (*Command, error) { p.nextToken() // consume SHOW - switch p.curToken.Type { case TokenVersion: p.nextToken() @@ -333,6 +383,10 @@ func (p *Parser) parseShowCommand() (*Command, error) { return p.parseShowInstance() case TokenBalance: return p.parseShowBalance() + case TokenTask: + return p.parseShowTask() + case TokenQuotedString: + return p.parseShowQuotedStringCommand() default: return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) } @@ -1454,6 +1508,27 @@ func (p *Parser) parseShowBalance() (*Command, error) { return cmd, nil } +// parseShowTask parses SHOW TASK +func (p *Parser) parseShowTask() (*Command, error) { + p.nextToken() // consume TASK + + taskID, err := p.parseQuotedString() + if err != nil { + return nil, fmt.Errorf("expected string: %w", err) + } + p.nextToken() + + cmd := NewCommand("show_task_user_command") + cmd.Params["task_id"] = taskID + p.nextToken() + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + // parseAlterInstance parses ALTER INSTANCE NAME FROM PROVIDER command func (p *Parser) parseAlterInstance() (*Command, error) { p.nextToken() // consume INSTANCE @@ -2900,6 +2975,45 @@ func (p *Parser) parseOCRCommand() (*Command, error) { return cmd, nil } +func (p *Parser) parseModelParseCommand() (*Command, error) { + p.nextToken() // consume WITH + + compositeModelName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + p.nextToken() + + cmd := NewCommand("parse_file_user_command") + + switch p.curToken.Type { + case TokenFile: + p.nextToken() + var file string + file, err = p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["file"] = file + p.nextToken() + case TokenURL: + p.nextToken() + var url string + url, err = p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["url"] = url + p.nextToken() + default: + return nil, fmt.Errorf("expected FILE or URL") + } + + cmd.Params["composite_model_name"] = compositeModelName + + return cmd, nil +} + func (p *Parser) parseCheckCommand() (*Command, error) { p.nextToken() // consume CHECK @@ -2964,11 +3078,14 @@ func (p *Parser) parseUseCommand() (*Command, error) { func (p *Parser) parseParseCommand() (*Command, error) { p.nextToken() // consume PARSE - if p.curToken.Type == TokenDataset { + switch p.curToken.Type { + case TokenDataset: return p.parseParseDataset() + case TokenWith: + return p.parseModelParseCommand() + default: + return p.parseParseDocs() } - - return p.parseParseDocs() } func (p *Parser) parseParseDataset() (*Command, error) { diff --git a/internal/engine/elasticsearch/index.go b/internal/engine/elasticsearch/index.go index 7e601acae3..b203969107 100644 --- a/internal/engine/elasticsearch/index.go +++ b/internal/engine/elasticsearch/index.go @@ -333,7 +333,7 @@ func (e *elasticsearchEngine) CreateMetadata(ctx context.Context, indexName stri // InsertDataset inserts documents into a dataset index func (e *elasticsearchEngine) InsertDataset(ctx context.Context, documents []map[string]interface{}, indexName string, knowledgebaseID string) ([]string, error) { - // TODO + // TODO return []string{}, nil } @@ -343,7 +343,6 @@ func (e *elasticsearchEngine) InsertMetadata(ctx context.Context, documents []ma return []string{}, nil } - // UpdateDataset updates a chunk by condition func (e *elasticsearchEngine) UpdateDataset(ctx context.Context, condition map[string]interface{}, newValue map[string]interface{}, tableNamePrefix string, knowledgebaseID string) error { // TODO diff --git a/internal/entity/models/aliyun.go b/internal/entity/models/aliyun.go index c89a959685..12ee525ca0 100644 --- a/internal/entity/models/aliyun.go +++ b/internal/entity/models/aliyun.go @@ -36,11 +36,6 @@ type AliyunModel struct { httpClient *http.Client // Reusable HTTP client with connection pool } -func (z *AliyunModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewAliyunModel creates a new Aliyun model instance func NewAliyunModel(baseURL map[string]string, urlSuffix URLSuffix) *AliyunModel { return &AliyunModel{ @@ -579,7 +574,12 @@ func (z *AliyunModel) AudioSpeechWithSender(modelName *string, audioContent *str } // OCRFile OCR file -func (z *AliyunModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (z *AliyunModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +// ParseFile parse file +func (z *AliyunModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { return nil, fmt.Errorf("%s, no such method", z.Name()) } @@ -670,3 +670,11 @@ func (z *AliyunModel) CheckConnection(apiConfig *APIConfig) error { } return nil } + +func (z *AliyunModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *AliyunModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/baichuan.go b/internal/entity/models/baichuan.go index 51ac9ae9d6..98c8f6752f 100644 --- a/internal/entity/models/baichuan.go +++ b/internal/entity/models/baichuan.go @@ -399,7 +399,12 @@ func (z *BaichuanModel) AudioSpeechWithSender(modelName *string, audioContent *s } // OCRFile OCR file -func (z *BaichuanModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (z *BaichuanModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +// ParseFile parse file +func (z *BaichuanModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { return nil, fmt.Errorf("%s, no such method", z.Name()) } @@ -414,3 +419,11 @@ func (b *BaichuanModel) Balance(apiConfig *APIConfig) (map[string]interface{}, e func (b *BaichuanModel) CheckConnection(apiConfig *APIConfig) error { return fmt.Errorf("no such method") } + +func (z *BaichuanModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *BaichuanModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/baidu.go b/internal/entity/models/baidu.go index 42576c1341..4b99ec7221 100644 --- a/internal/entity/models/baidu.go +++ b/internal/entity/models/baidu.go @@ -18,11 +18,6 @@ type BaiduModel struct { httpClient *http.Client } -func (b *BaiduModel) ParseFile() { - //TODO implement me - panic("implement me") -} - func (b *BaiduModel) NewInstance(baseURL map[string]string) ModelDriver { return &BaiduModel{ BaseURL: baseURL, @@ -626,10 +621,15 @@ func (z *BaiduModel) AudioSpeechWithSender(modelName *string, audioContent *stri } // OCRFile OCR file -func (b *BaiduModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (b *BaiduModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", b.Name()) } +// ParseFile parse file +func (z *BaiduModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + func (b *BaiduModel) ListModels(apiConfig *APIConfig) ([]string, error) { var region = "default" if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" { @@ -693,3 +693,11 @@ func (b *BaiduModel) CheckConnection(apiConfig *APIConfig) error { _, err := b.ListModels(apiConfig) return err } + +func (z *BaiduModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *BaiduModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/cohere.go b/internal/entity/models/cohere.go index c094bdd3c9..f078737ecf 100644 --- a/internal/entity/models/cohere.go +++ b/internal/entity/models/cohere.go @@ -17,11 +17,6 @@ type CoHereModel struct { httpClient *http.Client } -func (c *CoHereModel) ParseFile() { - //TODO implement me - panic("implement me") -} - func (c *CoHereModel) NewInstance(baseURL map[string]string) ModelDriver { return &CoHereModel{ BaseURL: baseURL, @@ -504,10 +499,15 @@ func (z *CoHereModel) AudioSpeechWithSender(modelName *string, audioContent *str } // OCRFile OCR file -func (c *CoHereModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (c *CoHereModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", c.Name()) } +// ParseFile parse file +func (z *CoHereModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + func (c *CoHereModel) ListModels(apiConfig *APIConfig) ([]string, error) { var region = "default" if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" { @@ -578,3 +578,11 @@ func (c *CoHereModel) CheckConnection(apiConfig *APIConfig) error { _, err := c.ListModels(apiConfig) return err } + +func (z *CoHereModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *CoHereModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/deepseek.go b/internal/entity/models/deepseek.go index 9d9a2406d0..7c809c71cf 100644 --- a/internal/entity/models/deepseek.go +++ b/internal/entity/models/deepseek.go @@ -36,11 +36,6 @@ type DeepSeekModel struct { httpClient *http.Client // Reusable HTTP client with connection pool } -func (z *DeepSeekModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewDeepSeekModel creates a new DeepSeek model instance func NewDeepSeekModel(baseURL map[string]string, urlSuffix URLSuffix) *DeepSeekModel { return &DeepSeekModel{ @@ -609,6 +604,19 @@ func (z *DeepSeekModel) AudioSpeechWithSender(modelName *string, audioContent *s } // OCRFile OCR file -func (d *DeepSeekModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (d *DeepSeekModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", d.Name()) } + +// ParseFile parse file +func (z *DeepSeekModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *DeepSeekModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *DeepSeekModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/dummy.go b/internal/entity/models/dummy.go index d2fb85095f..d80616bd04 100644 --- a/internal/entity/models/dummy.go +++ b/internal/entity/models/dummy.go @@ -26,11 +26,6 @@ type DummyModel struct { URLSuffix URLSuffix } -func (d *DummyModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewDummyModel creates a new Dummy AI model instance func NewDummyModel(baseURL map[string]string, urlSuffix URLSuffix) *DummyModel { return &DummyModel{ @@ -98,6 +93,19 @@ func (z *DummyModel) AudioSpeechWithSender(modelName *string, audioContent *stri } // OCRFile OCR file -func (d *DummyModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (d *DummyModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", d.Name()) } + +// ParseFile parse file +func (z *DummyModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *DummyModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *DummyModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/fishaudio.go b/internal/entity/models/fishaudio.go index 0eec1b1653..dc185e8975 100644 --- a/internal/entity/models/fishaudio.go +++ b/internal/entity/models/fishaudio.go @@ -24,11 +24,6 @@ type FishAudioModel struct { httpClient *http.Client } -func (f *FishAudioModel) ParseFile() { - //TODO implement me - panic("implement me") -} - func NewFishAudioModel(baseURL map[string]string, urlSuffix URLSuffix) *FishAudioModel { return &FishAudioModel{ BaseURL: baseURL, @@ -341,10 +336,15 @@ func (f *FishAudioModel) AudioSpeechWithSender(modelName *string, audioContent * } // OCRFile OCR file -func (f *FishAudioModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (f *FishAudioModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", f.Name()) } +// ParseFile parse file +func (z *FishAudioModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + func (f *FishAudioModel) ListModels(apiConfig *APIConfig) ([]string, error) { var region = "default" if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" { @@ -445,3 +445,11 @@ func (f *FishAudioModel) CheckConnection(apiConfig *APIConfig) error { _, err := f.ListModels(apiConfig) return err } + +func (z *FishAudioModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *FishAudioModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/gitee.go b/internal/entity/models/gitee.go index ad66dcd96e..70854f78b2 100644 --- a/internal/entity/models/gitee.go +++ b/internal/entity/models/gitee.go @@ -37,11 +37,6 @@ type GiteeModel struct { httpClient *http.Client // Reusable HTTP client with connection pool } -func (g *GiteeModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewGiteeModel creates a new Gitee model instance func NewGiteeModel(baseURL map[string]string, urlSuffix URLSuffix) *GiteeModel { return &GiteeModel{ @@ -623,7 +618,7 @@ type giteeOCRResponse struct { } // OCRFile OCR file -func (g *GiteeModel) OCRFile(modelName *string, content []byte, imageURL *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (g *GiteeModel) OCRFile(modelName *string, content []byte, imageURL *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { if imageURL == nil && content == nil { return nil, fmt.Errorf("url or content is required") } @@ -709,13 +704,174 @@ func (g *GiteeModel) OCRFile(modelName *string, content []byte, imageURL *string return nil, fmt.Errorf("failed to parse response: %w", err) } - var ocrResponse = OCRResponse{ + var ocrResponse = OCRFileResponse{ Text: &giteeResponse.Text, } return &ocrResponse, nil } +type giteeParseFileResponse struct { + TaskID string `json:"task_id"` + Status string `json:"status"` + CreatedAt int64 `json:"created_at"` + URLs giteeURLs `json:"urls"` +} + +type giteeURLs struct { + Get string `json:"get"` + Cancel string `json:"cancel"` +} + +// ParseFile parse file +func (g *GiteeModel) ParseFile(modelName *string, content []byte, documentURL *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + if documentURL == nil && content == nil { + return nil, fmt.Errorf("url or content is required") + } + + if apiConfig == nil || apiConfig.ApiKey == nil || *apiConfig.ApiKey == "" { + return nil, fmt.Errorf("api key is required") + } + + if modelName == nil || *modelName == "" { + return nil, fmt.Errorf("model name is required") + } + + region := "default" + if apiConfig.Region != nil && *apiConfig.Region != "" { + region = *apiConfig.Region + } + + baseURL := g.BaseURL["default"] + if region != "default" { + if regional, ok := g.BaseURL[region]; ok && regional != "" { + baseURL = regional + } + } + if baseURL == "" { + return nil, fmt.Errorf("gitee: no base URL configured for default region") + } + + url := fmt.Sprintf("%s/%s", strings.TrimSuffix(baseURL, "/"), g.URLSuffix.DocumentParse) + + payload := &bytes.Buffer{} + writer := multipart.NewWriter(payload) + + if err := writer.WriteField("model", *modelName); err != nil { + return nil, fmt.Errorf("failed to write model field: %w", err) + } + + if documentURL != nil { + if err := writer.WriteField("file", *documentURL); err != nil { + return nil, fmt.Errorf("failed to write file URL: %w", err) + } + } else if content != nil && len(content) > 0 { + part, err := writer.CreateFormFile("file", "file") + if err != nil { + return nil, fmt.Errorf("failed to create file form file: %w", err) + } + if _, err = part.Write(content); err != nil { + return nil, fmt.Errorf("failed to write file content: %w", err) + } + } else { + return nil, fmt.Errorf("file or file URL is required") + } + + writer.Close() + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, "POST", url, payload) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", writer.FormDataContentType()) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey)) + + resp, err := g.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("gitee OCR API error: %s, body: %s", resp.Status, string(body)) + } + + var giteeParseFileResp giteeParseFileResponse + if err = json.Unmarshal(body, &giteeParseFileResp); err != nil { + return nil, fmt.Errorf("failed to parse response: %w", err) + } + + _, err = g.getParseFile(&baseURL, apiConfig.ApiKey, &giteeParseFileResp.TaskID, 5*time.Second, 10) + if err != nil { + return nil, err + } + + var parseFileResponse = ParseFileResponse{} + + return &parseFileResponse, nil +} + +type giteeGetParseFileResponse struct { +} + +func (g *GiteeModel) getParseFile(baseURL *string, apiKey, taskID *string, timeOut time.Duration, count int) (*giteeGetParseFileResponse, error) { + url := fmt.Sprintf("%s/task/%s/status", strings.TrimSuffix(*baseURL, "/"), *taskID) + + reqBody := map[string]interface{}{} + + jsonData, err := json.Marshal(reqBody) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + req, err := http.NewRequest("GET", 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", *apiKey)) + + var resp *http.Response + for i := 0; i < count; i++ { + resp, err = g.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + var body []byte + 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)) + } + + // Parse response + var result map[string]interface{} + if err = json.Unmarshal(body, &result); err != nil { + return nil, fmt.Errorf("failed to parse response: %w", err) + } + + time.Sleep(timeOut) + } + + // if resp show the file is ok, download it. otherwise, provide timeout info + return nil, nil +} + func (g *GiteeModel) ListModels(apiConfig *APIConfig) ([]string, error) { var region = "default" if apiConfig.Region != nil { @@ -869,3 +1025,144 @@ func (g *GiteeModel) CheckConnection(apiConfig *APIConfig) error { return nil } + +type giteeTaskListResponse struct { + Total int `json:"total"` + Items []giteeTaskItem `json:"items"` +} + +type giteeTaskItem struct { + TaskID string `json:"task_id"` + //Output giteeTaskOutput `json:"output"` + Status string `json:"status"` + CreatedAt int64 `json:"created_at"` + StartedAt int64 `json:"started_at,omitempty"` + CompletedAt int64 `json:"completed_at,omitempty"` + Price float64 `json:"price"` + Currency string `json:"currency"` + URLs giteeTaskURLs `json:"urls"` +} + +type giteeTaskOutput struct { + Segments []giteeSegment `json:"segments"` +} + +type giteeSegment struct { + Index int `json:"index"` + Content string `json:"content"` +} +type giteeTaskURLs struct { + Get string `json:"get"` + Cancel string `json:"cancel"` +} + +func (g *GiteeModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + var region = "default" + if apiConfig.Region != nil { + region = *apiConfig.Region + } + + url := fmt.Sprintf("%s/%s", g.BaseURL[region], g.URLSuffix.Tasks) + + // Build request body + reqBody := map[string]interface{}{} + + jsonData, err := json.Marshal(reqBody) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + req, err := http.NewRequest("GET", 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 := g.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + var body []byte + body, err = io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response: %w", err) + } + + var giteeTaskList giteeTaskListResponse + if err = json.Unmarshal(body, &giteeTaskList); err != nil { + return nil, fmt.Errorf("failed to parse response: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) + } + + taskListResp := []ListTaskStatus{} + for _, item := range giteeTaskList.Items { + taskListResp = append(taskListResp, ListTaskStatus{ + TaskID: item.TaskID, + Status: item.Status, + }) + } + return taskListResp, nil +} + +func (g *GiteeModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + var region = "default" + if apiConfig.Region != nil { + region = *apiConfig.Region + } + + url := fmt.Sprintf("%s/%s/%s/get", g.BaseURL[region], g.URLSuffix.Task, taskID) + + // Build request body + reqBody := map[string]interface{}{} + + jsonData, err := json.Marshal(reqBody) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + req, err := http.NewRequest("GET", 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 := g.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 taskOutput giteeTaskOutput + if err = json.Unmarshal(body, &taskOutput); err != nil { + return nil, fmt.Errorf("failed to parse response: %w", err) + } + + taskResp := &TaskResponse{} + + for _, segment := range taskOutput.Segments { + taskResp.Segments = append(taskResp.Segments, TaskSegment{ + Index: segment.Index, + Content: segment.Content, + }) + } + + return taskResp, nil +} diff --git a/internal/entity/models/google.go b/internal/entity/models/google.go index 2e1f103dae..5578cdadd7 100644 --- a/internal/entity/models/google.go +++ b/internal/entity/models/google.go @@ -77,11 +77,6 @@ type GoogleModel struct { URLSuffix URLSuffix } -func (g *GoogleModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewGoogleModel creates a new Google AI model instance func NewGoogleModel(baseURL map[string]string, urlSuffix URLSuffix) *GoogleModel { return &GoogleModel{ @@ -364,6 +359,19 @@ func (z *GoogleModel) AudioSpeechWithSender(modelName *string, audioContent *str } // OCRFile OCR file -func (g *GoogleModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (g *GoogleModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", g.Name()) } + +// ParseFile parse file +func (z *GoogleModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *GoogleModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *GoogleModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/huggingface.go b/internal/entity/models/huggingface.go index 41aee10f42..a730e55635 100644 --- a/internal/entity/models/huggingface.go +++ b/internal/entity/models/huggingface.go @@ -19,11 +19,6 @@ type HuggingFaceModel struct { httpClient *http.Client } -func (h *HuggingFaceModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewHuggingFaceModel creates a new huggingFace model instance func NewHuggingFaceModel(baseURL map[string]string, urlSuffix URLSuffix) *HuggingFaceModel { return &HuggingFaceModel{ @@ -435,10 +430,15 @@ func (z *HuggingFaceModel) AudioSpeechWithSender(modelName *string, audioContent } // OCRFile OCR file -func (h *HuggingFaceModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (h *HuggingFaceModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", h.Name()) } +// ParseFile parse file +func (z *HuggingFaceModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + func (h *HuggingFaceModel) ListModels(apiConfig *APIConfig) ([]string, error) { var region = "default" if apiConfig.Region != nil && *apiConfig.Region != "" { @@ -502,3 +502,11 @@ func (h *HuggingFaceModel) CheckConnection(apiConfig *APIConfig) error { _, err := h.ListModels(apiConfig) return err } + +func (z *HuggingFaceModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *HuggingFaceModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/jina.go b/internal/entity/models/jina.go index 7a9f1c02c7..86377fa330 100644 --- a/internal/entity/models/jina.go +++ b/internal/entity/models/jina.go @@ -270,6 +270,19 @@ func (z *JinaModel) AudioSpeechWithSender(modelName *string, audioContent *strin } // OCRFile OCR file -func (z *JinaModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (z *JinaModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +// ParseFile parse file +func (z *JinaModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *JinaModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *JinaModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { return nil, fmt.Errorf("%s, no such method", z.Name()) } diff --git a/internal/entity/models/lmstudio.go b/internal/entity/models/lmstudio.go index fa2e2dbf95..e9767a511d 100644 --- a/internal/entity/models/lmstudio.go +++ b/internal/entity/models/lmstudio.go @@ -20,11 +20,6 @@ type LmStudioModel struct { httpClient *http.Client } -func (l *LmStudioModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewLmStudioModel func NewLmStudioModel(baseURL map[string]string, urlSuffix URLSuffix) *LmStudioModel { return &LmStudioModel{ @@ -471,10 +466,15 @@ func (z *LmStudioModel) AudioSpeechWithSender(modelName *string, audioContent *s } // OCRFile OCR file -func (l *LmStudioModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (l *LmStudioModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", l.Name()) } +// ParseFile parse file +func (z *LmStudioModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + // ListModels list supported models func (l *LmStudioModel) ListModels(apiConfig *APIConfig) ([]string, error) { var region = "default" @@ -553,3 +553,11 @@ func (l *LmStudioModel) CheckConnection(apiConfig *APIConfig) error { _, err := l.ListModels(apiConfig) return err } + +func (z *LmStudioModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *LmStudioModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/localai.go b/internal/entity/models/localai.go index f5fab0df3e..bc2b1b0300 100644 --- a/internal/entity/models/localai.go +++ b/internal/entity/models/localai.go @@ -819,6 +819,19 @@ func (l *LocalAIModel) AudioSpeechWithSender(modelName *string, audioContent *st // OCRFile: LocalAI has no OCR pipeline in its OpenAI-compatible surface; // document parsing belongs to a different interface entirely. -func (l *LocalAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (l *LocalAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", l.Name()) } + +// ParseFile parse file +func (z *LocalAIModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *LocalAIModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *LocalAIModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/longcat.go b/internal/entity/models/longcat.go index b954318846..b5f194e5a2 100644 --- a/internal/entity/models/longcat.go +++ b/internal/entity/models/longcat.go @@ -459,6 +459,19 @@ func (l *LongCatModel) AudioSpeechWithSender(modelName *string, audioContent *st } // OCRFile is not exposed by the LongCat API. -func (l *LongCatModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (l *LongCatModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", l.Name()) } + +// ParseFile parse file +func (z *LongCatModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *LongCatModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *LongCatModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/minimax.go b/internal/entity/models/minimax.go index 683a8dc454..749d0d52e8 100644 --- a/internal/entity/models/minimax.go +++ b/internal/entity/models/minimax.go @@ -36,11 +36,6 @@ type MinimaxModel struct { httpClient *http.Client // Reusable HTTP client with connection pool } -func (z *MinimaxModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewMinimaxModel creates a new Minimax model instance func NewMinimaxModel(baseURL map[string]string, urlSuffix URLSuffix) *MinimaxModel { return &MinimaxModel{ @@ -667,6 +662,19 @@ func (z *MinimaxModel) AudioSpeechWithSender(modelName *string, audioContent *st } // OCRFile OCR file -func (m *MinimaxModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (m *MinimaxModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", m.Name()) -} \ No newline at end of file +} + +// ParseFile parse file +func (z *MinimaxModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *MinimaxModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *MinimaxModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/mistral.go b/internal/entity/models/mistral.go index 11ba87ae97..e1303346fa 100644 --- a/internal/entity/models/mistral.go +++ b/internal/entity/models/mistral.go @@ -583,6 +583,19 @@ func (z *MistralModel) AudioSpeechWithSender(modelName *string, audioContent *st } // OCRFile OCR file -func (z *MistralModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (z *MistralModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +// ParseFile parse file +func (z *MistralModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *MistralModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *MistralModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { return nil, fmt.Errorf("%s, no such method", z.Name()) } diff --git a/internal/entity/models/moonshot.go b/internal/entity/models/moonshot.go index 652433b7aa..3156b49cb9 100644 --- a/internal/entity/models/moonshot.go +++ b/internal/entity/models/moonshot.go @@ -35,11 +35,6 @@ type MoonshotModel struct { httpClient *http.Client // Reusable HTTP client with connection pool } -func (m *MoonshotModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewMoonshotModel creates a new Moonshot model instance func NewMoonshotModel(baseURL map[string]string, urlSuffix URLSuffix) *MoonshotModel { return &MoonshotModel{ @@ -512,6 +507,19 @@ func (z *MoonshotModel) AudioSpeechWithSender(modelName *string, audioContent *s } // OCRFile OCR file -func (m *MoonshotModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (m *MoonshotModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", m.Name()) } + +// ParseFile parse file +func (z *MoonshotModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *MoonshotModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *MoonshotModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/novita.go b/internal/entity/models/novita.go index ef48886e87..5eb58efd7c 100644 --- a/internal/entity/models/novita.go +++ b/internal/entity/models/novita.go @@ -658,6 +658,19 @@ func (n *NovitaModel) AudioSpeechWithSender(modelName *string, audioContent *str } // OCRFile OCR file -func (n *NovitaModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (n *NovitaModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", n.Name()) } + +// ParseFile parse file +func (z *NovitaModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *NovitaModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *NovitaModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/nvidia.go b/internal/entity/models/nvidia.go index e1cb9daaba..bb1e6785c7 100644 --- a/internal/entity/models/nvidia.go +++ b/internal/entity/models/nvidia.go @@ -19,11 +19,6 @@ type NvidiaModel struct { httpClient *http.Client } -func (n NvidiaModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewNvidiaModel creates a new Nvidia model instance func NewNvidiaModel(baseURL map[string]string, urlSuffix URLSuffix) *NvidiaModel { return &NvidiaModel{ @@ -576,10 +571,15 @@ func (z *NvidiaModel) AudioSpeechWithSender(modelName *string, audioContent *str } // OCRFile OCR file -func (m *NvidiaModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (m *NvidiaModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", m.Name()) } +// ParseFile parse file +func (z *NvidiaModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + // ListModels calls /v1/models on the configured NVIDIA NIM base URL // and returns the list of available model ids. The endpoint is // OpenAI-compatible, so the parsing follows the same shape used by @@ -662,3 +662,11 @@ func (n NvidiaModel) CheckConnection(apiConfig *APIConfig) error { _, err := n.ListModels(apiConfig) return err } + +func (z *NvidiaModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *NvidiaModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/ollama.go b/internal/entity/models/ollama.go index 01465c8405..b4eb1b35d9 100644 --- a/internal/entity/models/ollama.go +++ b/internal/entity/models/ollama.go @@ -20,11 +20,6 @@ type OllamaModel struct { httpClient *http.Client } -func (o *OllamaModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewOllamaModel creates a new Ollama AI model instance func NewOllamaModel(baseURL map[string]string, urlSuffix URLSuffix) *OllamaModel { return &OllamaModel{ @@ -469,10 +464,15 @@ func (z *OllamaModel) AudioSpeechWithSender(modelName *string, audioContent *str } // OCRFile OCR file -func (m *OllamaModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (m *OllamaModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", m.Name()) } +// ParseFile parse file +func (z *OllamaModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + func (o *OllamaModel) ListModels(apiConfig *APIConfig) ([]string, error) { var region = "default" @@ -551,3 +551,11 @@ func (o *OllamaModel) CheckConnection(apiConfig *APIConfig) error { _, err := o.ListModels(apiConfig) return err } + +func (z *OllamaModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *OllamaModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/openai.go b/internal/entity/models/openai.go index 0f96386dd9..e46b734bb1 100644 --- a/internal/entity/models/openai.go +++ b/internal/entity/models/openai.go @@ -37,11 +37,6 @@ type OpenAIModel struct { httpClient *http.Client // Reusable HTTP client with connection pool } -func (o *OpenAIModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewOpenAIModel creates a new OpenAI model instance. // // We clone http.DefaultTransport so we keep Go's defaults for @@ -618,6 +613,19 @@ func (z *OpenAIModel) AudioSpeechWithSender(modelName *string, audioContent *str } // OCRFile OCR file -func (m *OpenAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (m *OpenAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", m.Name()) } + +// ParseFile parse file +func (z *OpenAIModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *OpenAIModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *OpenAIModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/openrouter.go b/internal/entity/models/openrouter.go index 461a1fe4c3..3b885d40f3 100644 --- a/internal/entity/models/openrouter.go +++ b/internal/entity/models/openrouter.go @@ -19,11 +19,6 @@ type OpenRouterModel struct { httpClient *http.Client } -func (o *OpenRouterModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewOpenRouterModel creates a new OpenRouter AI model instance func NewOpenRouterModel(baseURL map[string]string, urlSuffix URLSuffix) *OpenRouterModel { return &OpenRouterModel{ @@ -610,10 +605,15 @@ func (z *OpenRouterModel) AudioSpeechWithSender(modelName *string, audioContent } // OCRFile OCR file -func (m *OpenRouterModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (m *OpenRouterModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", m.Name()) } +// ParseFile parse file +func (z *OpenRouterModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + func (o *OpenRouterModel) ListModels(apiConfig *APIConfig) ([]string, error) { var region = "default" if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" { @@ -726,3 +726,11 @@ func (o *OpenRouterModel) CheckConnection(apiConfig *APIConfig) error { _, err := o.Balance(apiConfig) return err } + +func (z *OpenRouterModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *OpenRouterModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/siliconflow.go b/internal/entity/models/siliconflow.go index b31d53898b..8a823c2ba2 100644 --- a/internal/entity/models/siliconflow.go +++ b/internal/entity/models/siliconflow.go @@ -36,11 +36,6 @@ type SiliconflowModel struct { httpClient *http.Client // Reusable HTTP client with connection pool } -func (s *SiliconflowModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewSiliconflowModel creates a new Siliconflow model instance func NewSiliconflowModel(baseURL map[string]string, urlSuffix URLSuffix) *SiliconflowModel { return &SiliconflowModel{ @@ -745,6 +740,19 @@ func (z *SiliconflowModel) AudioSpeechWithSender(modelName *string, audioContent } // OCRFile OCR file -func (m *SiliconflowModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (m *SiliconflowModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", m.Name()) } + +// ParseFile parse file +func (z *SiliconflowModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *SiliconflowModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *SiliconflowModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/stepfun.go b/internal/entity/models/stepfun.go index d632313d98..30d226e250 100644 --- a/internal/entity/models/stepfun.go +++ b/internal/entity/models/stepfun.go @@ -477,6 +477,19 @@ func (z *StepFunModel) AudioSpeechWithSender(modelName *string, audioContent *st } // OCRFile OCR file -func (z *StepFunModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (z *StepFunModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +// ParseFile parse file +func (z *StepFunModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *StepFunModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *StepFunModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { return nil, fmt.Errorf("%s, no such method", z.Name()) } diff --git a/internal/entity/models/types.go b/internal/entity/models/types.go index b0bc9b46b0..e4123e064d 100644 --- a/internal/entity/models/types.go +++ b/internal/entity/models/types.go @@ -17,12 +17,11 @@ type ModelDriver interface { Name() string - // ChatWithMessages sends multiple messages with role and content + // ChatWithMessages sends multiple messages synchronously ChatWithMessages(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig) (*ChatResponse, error) - // ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel) - // messages accepts []Message which supports multimodal content (e.g., [{"type": "text", "text": "..."}, {"type": "image_url", "image_url": {"url": "..."}}]) + // ChatStreamlyWithSender sends multiple messages asynchronously ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, modelConfig *ChatConfig, sender func(*string, *string) error) error - // Encode encodes a list of texts into embeddings + // Embed a list of texts into embeddings Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig) ([]EmbeddingData, error) // Rerank calculates similarity scores between query and texts Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig) (*RerankResponse, error) @@ -33,13 +32,19 @@ type ModelDriver interface { AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, asrConfig *TTSConfig) (*TTSResponse, error) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, sender func(*string, *string) error) error // OCRFile OCR file - OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) + OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) + // ParseFile parse file + ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) // ListModels List supported models ListModels(apiConfig *APIConfig) ([]string, error) Balance(apiConfig *APIConfig) (map[string]interface{}, error) CheckConnection(apiConfig *APIConfig) error + + ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) + + ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) } type ChatResponse struct { @@ -69,24 +74,44 @@ type TTSResponse struct { Audio []byte `json:"audio"` } -type OCRResponse struct { +type OCRFileResponse struct { Text *string `json:"text"` } +type ParseFileResponse struct { +} + +type ListTaskStatus struct { + TaskID string `json:"task_id"` + Status string `json:"status"` +} + +type TaskSegment struct { + Index int `json:"index"` + Content string `json:"content"` +} + +type TaskResponse struct { + Segments []TaskSegment `json:"segments"` +} + // URLSuffix represents the URL suffixes for different API endpoints type URLSuffix struct { - Chat string `json:"chat"` - AsyncChat string `json:"async_chat"` - AsyncResult string `json:"async_result"` - Embedding string `json:"embedding"` - Rerank string `json:"rerank"` - OCR string `json:"ocr"` - Models string `json:"models"` - Balance string `json:"balance"` - Files string `json:"files"` - Status string `json:"status"` - TTS string `json:"tts"` - ASR string `json:"asr"` + Chat string `json:"chat"` + AsyncChat string `json:"async_chat"` + AsyncResult string `json:"async_result"` + Embedding string `json:"embedding"` + Rerank string `json:"rerank"` + TTS string `json:"tts"` + ASR string `json:"asr"` + OCR string `json:"ocr"` + DocumentParse string `json:"doc_parse"` + Models string `json:"models"` + Balance string `json:"balance"` + Files string `json:"files"` + Status string `json:"status"` + Tasks string `json:"tasks"` + Task string `json:"task"` } type ChatConfig struct { @@ -128,6 +153,9 @@ type TTSConfig struct { type OCRConfig struct { } +type ParseFileConfig struct { +} + // EmbeddingModel wraps a ModelDriver with embedding-specific configuration type EmbeddingModel struct { ModelDriver ModelDriver diff --git a/internal/entity/models/upstage.go b/internal/entity/models/upstage.go index 2991bb82ce..7e5596a902 100644 --- a/internal/entity/models/upstage.go +++ b/internal/entity/models/upstage.go @@ -604,6 +604,19 @@ func (z *UpstageModel) AudioSpeechWithSender(modelName *string, audioContent *st } // OCRFile OCR file -func (z *UpstageModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (z *UpstageModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +// ParseFile parse file +func (z *UpstageModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *UpstageModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *UpstageModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { return nil, fmt.Errorf("%s, no such method", z.Name()) } diff --git a/internal/entity/models/vllm.go b/internal/entity/models/vllm.go index 4933f48fc2..4780fee939 100644 --- a/internal/entity/models/vllm.go +++ b/internal/entity/models/vllm.go @@ -36,11 +36,6 @@ type VllmModel struct { httpClient *http.Client // Reusable HTTP client with connection pool } -func (v *VllmModel) ParseFile() { - //TODO implement me - panic("implement me") -} - // NewVllmModel creates a new Vllm AI model instance func NewVllmModel(baseURL map[string]string, urlSuffix URLSuffix) *VllmModel { return &VllmModel{ @@ -576,6 +571,19 @@ func (z *VllmModel) AudioSpeechWithSender(modelName *string, audioContent *strin } // OCRFile OCR file -func (m *VllmModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (m *VllmModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", m.Name()) } + +// ParseFile parse file +func (z *VllmModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *VllmModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *VllmModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/volcengine.go b/internal/entity/models/volcengine.go index 5ab5f1f107..4a806883ec 100644 --- a/internal/entity/models/volcengine.go +++ b/internal/entity/models/volcengine.go @@ -529,10 +529,15 @@ func (z *VolcEngine) AudioSpeechWithSender(modelName *string, audioContent *stri } // OCRFile OCR file -func (m *VolcEngine) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (m *VolcEngine) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", m.Name()) } +// ParseFile parse file +func (z *VolcEngine) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + func (z *VolcEngine) ListModels(apiConfig *APIConfig) ([]string, error) { var region = "default" if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" { @@ -627,3 +632,11 @@ func (z *VolcEngine) CheckConnection(apiConfig *APIConfig) error { return nil } + +func (z *VolcEngine) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *VolcEngine) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/voyage.go b/internal/entity/models/voyage.go index 41d0237c7e..14f67dda75 100644 --- a/internal/entity/models/voyage.go +++ b/internal/entity/models/voyage.go @@ -371,6 +371,19 @@ func (v *VoyageModel) AudioSpeechWithSender(modelName *string, audioContent *str return fmt.Errorf("%s, no such method", v.Name()) } -func (v *VoyageModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (v *VoyageModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", v.Name()) } + +// ParseFile parse file +func (z *VoyageModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *VoyageModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *VoyageModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/xai.go b/internal/entity/models/xai.go index 253f1bd195..88b465fecf 100644 --- a/internal/entity/models/xai.go +++ b/internal/entity/models/xai.go @@ -512,6 +512,19 @@ func (z *XAIModel) AudioSpeechWithSender(modelName *string, audioContent *string } // OCRFile OCR file -func (m *XAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (m *XAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", m.Name()) } + +// ParseFile parse file +func (z *XAIModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *XAIModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *XAIModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/entity/models/zhipu-ai.go b/internal/entity/models/zhipu-ai.go index 5a7c88acb5..fa86c15467 100644 --- a/internal/entity/models/zhipu-ai.go +++ b/internal/entity/models/zhipu-ai.go @@ -686,6 +686,19 @@ func (z *ZhipuAIModel) AudioSpeechWithSender(modelName *string, audioContent *st } // OCRFile OCR file -func (m *ZhipuAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRResponse, error) { +func (m *ZhipuAIModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { return nil, fmt.Errorf("%s, no such method", m.Name()) } + +// ParseFile parse file +func (z *ZhipuAIModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *ZhipuAIModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} + +func (z *ZhipuAIModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { + return nil, fmt.Errorf("%s, no such method", z.Name()) +} diff --git a/internal/handler/memory.go b/internal/handler/memory.go index b8e04d06d8..745cd9ddcc 100644 --- a/internal/handler/memory.go +++ b/internal/handler/memory.go @@ -194,7 +194,7 @@ func (h *MemoryHandler) CreateMemory(c *gin.Context) { // Return success response c.JSON(http.StatusOK, gin.H{ "code": common.CodeSuccess, - "message": "success", + "message": "success", "data": result, }) } @@ -293,7 +293,7 @@ func (h *MemoryHandler) UpdateMemory(c *gin.Context) { // Return success response c.JSON(http.StatusOK, gin.H{ "code": common.CodeSuccess, - "message": "success", + "message": "success", "data": result, }) } @@ -347,7 +347,7 @@ func (h *MemoryHandler) DeleteMemory(c *gin.Context) { // Return success response c.JSON(http.StatusOK, gin.H{ "code": common.CodeSuccess, - "message": "success", + "message": "success", "data": nil, }) } @@ -436,7 +436,7 @@ func (h *MemoryHandler) ListMemories(c *gin.Context) { // Return success response c.JSON(http.StatusOK, gin.H{ "code": common.CodeSuccess, - "message": "success", + "message": "success", "data": result, }) } @@ -490,7 +490,7 @@ func (h *MemoryHandler) GetMemoryConfig(c *gin.Context) { // Return success response c.JSON(http.StatusOK, gin.H{ "code": common.CodeSuccess, - "message": "success", + "message": "success", "data": result, }) } diff --git a/internal/handler/providers.go b/internal/handler/providers.go index c6d61a2e65..9f7b238fc6 100644 --- a/internal/handler/providers.go +++ b/internal/handler/providers.go @@ -423,6 +423,91 @@ func (h *ProviderHandler) CheckProviderConnection(c *gin.Context) { }) } +func (h *ProviderHandler) ListTasks(c *gin.Context) { + providerName := c.Param("provider_name") + if providerName == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "Provider name is required", + }) + return + } + + instanceName := c.Param("instance_name") + if instanceName == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "Instance name is required", + }) + return + } + + userID := c.GetString("user_id") + + // Get tenant ID from user + listTaskResponse, errorCode, err := h.modelProviderService.ListTasks(providerName, instanceName, userID) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "code": errorCode, + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "message": "success", + "data": listTaskResponse, + }) +} + +func (h *ProviderHandler) ShowTask(c *gin.Context) { + providerName := c.Param("provider_name") + if providerName == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "Provider name is required", + }) + return + } + + instanceName := c.Param("instance_name") + if instanceName == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "Instance name is required", + }) + return + } + + taskID := c.Param("task_id") + if taskID == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "Task id is required", + }) + return + } + + userID := c.GetString("user_id") + + // Get tenant ID from user + taskResponse, errorCode, err := h.modelProviderService.ShowTask(providerName, instanceName, taskID, userID) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "code": errorCode, + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "message": "success", + "data": taskResponse, + }) +} + type AlterProviderInstanceRequest struct { LLMName string `json:"llm_name" binding:"required"` } @@ -1341,7 +1426,7 @@ func (h *ProviderHandler) OCRFile(c *gin.Context) { OCRConfig := models.OCRConfig{} // Non-stream response - var response *models.OCRResponse + var response *models.OCRFileResponse var errorCode common.ErrorCode var err error @@ -1361,3 +1446,77 @@ func (h *ProviderHandler) OCRFile(c *gin.Context) { "message": "success", }) } + +type ParseFileRequest struct { + ProviderName *string `json:"provider_name"` + InstanceName *string `json:"instance_name"` + ModelName *string `json:"model_name"` + Content []byte `json:"content"` + URL *string `json:"url"` +} + +func (h *ProviderHandler) ParseFile(c *gin.Context) { + var req ParseFileRequest + if err := c.ShouldBindJSON(&req); err != nil { + println("JSON bind error: %v (type: %T)", err, err) + c.JSON(http.StatusOK, gin.H{ + "code": common.CodeBadRequest, + "message": err.Error(), + }) + return + } + + if req.ProviderName == nil || *req.ProviderName == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "Provider name is required", + }) + return + } + + if req.InstanceName == nil || *req.InstanceName == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "Instance name is required", + }) + return + } + + if req.ModelName == nil || *req.ModelName == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "Model name is required", + }) + return + } + + userID := c.GetString("user_id") + + apiConfig := models.APIConfig{ + ApiKey: nil, + Region: nil, + } + + parseFileConfig := models.ParseFileConfig{} + + // Non-stream response + var response *models.ParseFileResponse + var errorCode common.ErrorCode + var err error + + response, errorCode, err = h.modelProviderService.ParseFile(*req.ProviderName, *req.InstanceName, *req.ModelName, userID, req.Content, req.URL, &apiConfig, &parseFileConfig) + + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "code": errorCode, + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "data": response, + "message": "success", + }) +} diff --git a/internal/router/router.go b/internal/router/router.go index 05a56ff8c8..4354551b9a 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -263,6 +263,8 @@ func (r *Router) Setup(engine *gin.Engine) { provider.GET("/:provider_name/instances/:instance_name", r.providerHandler.ShowProviderInstance) provider.GET("/:provider_name/instances/:instance_name/balance", r.providerHandler.ShowInstanceBalance) provider.GET("/:provider_name/instances/:instance_name/connection", r.providerHandler.CheckProviderConnection) + provider.GET("/:provider_name/instances/:instance_name/tasks", r.providerHandler.ListTasks) + provider.GET("/:provider_name/instances/:instance_name/tasks/:task_id", r.providerHandler.ShowTask) provider.PUT("/:provider_name/instances/:instance_name", r.providerHandler.AlterProviderInstance) provider.DELETE("/:provider_name/instances", r.providerHandler.DropProviderInstance) provider.GET("/:provider_name/instances/:instance_name/models", r.providerHandler.ListInstanceModels) @@ -275,6 +277,7 @@ func (r *Router) Setup(engine *gin.Engine) { v1.POST("/audio/transcriptions", r.providerHandler.TranscribeAudio) v1.POST("/audio/speech", r.providerHandler.AudioSpeech) v1.POST("/file/ocr", r.providerHandler.OCRFile) + v1.POST("/file/parse", r.providerHandler.ParseFile) } model := v1.Group("/models") diff --git a/internal/server/config.go b/internal/server/config.go index 25f1b41876..27e97b2472 100644 --- a/internal/server/config.go +++ b/internal/server/config.go @@ -723,6 +723,23 @@ func FromConfigFile(configPath string) error { } } + if v.IsSet("minio_0") { + minioConfig := v.Sub("minio_0") + if minioConfig != nil { + if globalConfig.StorageEngine.Minio == nil { + globalConfig.StorageEngine.Minio = &MinioConfig{ + Host: minioConfig.GetString("host"), + User: minioConfig.GetString("user"), + Password: minioConfig.GetString("password"), + Secure: minioConfig.GetBool("secure"), + PrefixPath: minioConfig.GetString("prefix_path"), + Verify: minioConfig.GetBool("verify"), + Bucket: minioConfig.GetString("bucket"), + } + } + } + } + if v.IsSet("s3") { s3Config := v.Sub("s3") if s3Config != nil { diff --git a/internal/service/model_service.go b/internal/service/model_service.go index f23f962109..fe179d54e2 100644 --- a/internal/service/model_service.go +++ b/internal/service/model_service.go @@ -460,6 +460,128 @@ func (m *ModelProviderService) CheckProviderConnection(providerName, instanceNam return common.CodeSuccess, nil } +func (m *ModelProviderService) ListTasks(providerName, instanceName, userID string) ([]modelModule.ListTaskStatus, common.ErrorCode, error) { + + // Get tenant ID from user + tenants, err := m.userTenantDAO.GetByUserIDAndRole(userID, "owner") + if err != nil { + return nil, common.CodeServerError, err + } + + if len(tenants) == 0 { + return nil, common.CodeNotFound, errors.New("user has no tenants") + } + + tenantID := tenants[0].TenantID + + // Check if provider exists + provider, err := m.modelProviderDAO.GetByTenantIDAndProviderName(tenantID, providerName) + if err != nil { + return nil, common.CodeServerError, err + } + + instance, err := m.modelInstanceDAO.GetByProviderIDAndInstanceName(provider.ID, instanceName) + if err != nil { + return nil, common.CodeServerError, err + } + + providerInfo := dao.GetModelProviderManager().FindProvider(providerName) + if providerInfo == nil { + return nil, common.CodeServerError, fmt.Errorf("provider %s not found", providerName) + } + + var extra map[string]string + err = json.Unmarshal([]byte(instance.Extra), &extra) + if err != nil { + return nil, common.CodeServerError, err + } + + apiConfig := &modelModule.APIConfig{ + ApiKey: nil, + Region: nil, + } + + region := extra["region"] + apiConfig.Region = ®ion + apiConfig.ApiKey = &instance.APIKey + + driver := providerInfo.ModelDriver + if baseURL, ok := extra["base_url"]; ok && baseURL != "" { + newURL := map[string]string{ + region: baseURL, + } + driver = driver.NewInstance(newURL) + } + + var listTaskResponse []modelModule.ListTaskStatus + listTaskResponse, err = driver.ListTasks(apiConfig) + if err != nil { + return nil, common.CodeServerError, err + } + return listTaskResponse, common.CodeSuccess, nil +} + +func (m *ModelProviderService) ShowTask(providerName, instanceName, taskID, userID string) (*modelModule.TaskResponse, common.ErrorCode, error) { + + // Get tenant ID from user + tenants, err := m.userTenantDAO.GetByUserIDAndRole(userID, "owner") + if err != nil { + return nil, common.CodeServerError, err + } + + if len(tenants) == 0 { + return nil, common.CodeNotFound, errors.New("user has no tenants") + } + + tenantID := tenants[0].TenantID + + // Check if provider exists + provider, err := m.modelProviderDAO.GetByTenantIDAndProviderName(tenantID, providerName) + if err != nil { + return nil, common.CodeServerError, err + } + + instance, err := m.modelInstanceDAO.GetByProviderIDAndInstanceName(provider.ID, instanceName) + if err != nil { + return nil, common.CodeServerError, err + } + + providerInfo := dao.GetModelProviderManager().FindProvider(providerName) + if providerInfo == nil { + return nil, common.CodeServerError, fmt.Errorf("provider %s not found", providerName) + } + + var extra map[string]string + err = json.Unmarshal([]byte(instance.Extra), &extra) + if err != nil { + return nil, common.CodeServerError, err + } + + apiConfig := &modelModule.APIConfig{ + ApiKey: nil, + Region: nil, + } + + region := extra["region"] + apiConfig.Region = ®ion + apiConfig.ApiKey = &instance.APIKey + + driver := providerInfo.ModelDriver + if baseURL, ok := extra["base_url"]; ok && baseURL != "" { + newURL := map[string]string{ + region: baseURL, + } + driver = driver.NewInstance(newURL) + } + + var taskResponse *modelModule.TaskResponse + taskResponse, err = driver.ShowTask(taskID, apiConfig) + if err != nil { + return nil, common.CodeServerError, err + } + return taskResponse, common.CodeSuccess, nil +} + func (m *ModelProviderService) AlterProviderInstance(providerName, instanceName, newInstanceName, apiKey, userID string) (common.ErrorCode, error) { return common.CodeSuccess, nil } @@ -1518,7 +1640,7 @@ func (m *ModelProviderService) AudioSpeechStream(providerName, instanceName, mod return common.CodeServerError, errors.New("model is disabled") } -func (m *ModelProviderService) OCRFile(providerName, instanceName, modelName, userID string, content []byte, url *string, apiConfig *modelModule.APIConfig, ocrConfig *modelModule.OCRConfig) (*modelModule.OCRResponse, common.ErrorCode, error) { +func (m *ModelProviderService) OCRFile(providerName, instanceName, modelName, userID string, content []byte, url *string, apiConfig *modelModule.APIConfig, ocrConfig *modelModule.OCRConfig) (*modelModule.OCRFileResponse, common.ErrorCode, error) { if apiConfig == nil { apiConfig = &modelModule.APIConfig{} } @@ -1563,7 +1685,7 @@ func (m *ModelProviderService) OCRFile(providerName, instanceName, modelName, us } if !model.ModelTypeMap["ocr"] { - return nil, common.CodeNotFound, errors.New(fmt.Sprintf("provider %s model %s is not a OCR model", providerName, modelName)) + return nil, common.CodeNotFound, errors.New(fmt.Sprintf("provider %s model %s is not an OCR model", providerName, modelName)) } var extra map[string]string @@ -1576,7 +1698,7 @@ func (m *ModelProviderService) OCRFile(providerName, instanceName, modelName, us apiConfig.Region = ®ion apiConfig.ApiKey = &instance.APIKey - var response *modelModule.OCRResponse + var response *modelModule.OCRFileResponse response, err = providerInfo.ModelDriver.OCRFile(&modelName, content, url, apiConfig, ocrConfig) if err != nil { return nil, common.CodeServerError, err @@ -1589,7 +1711,7 @@ func (m *ModelProviderService) OCRFile(providerName, instanceName, modelName, us } if modelInfo.Status == "active" { - if modelInfo.ModelType != "tts" { + if modelInfo.ModelType != "ocr" { return nil, common.CodeServerError, errors.New(fmt.Sprintf("expect model %s@%s is an OCR model", modelName, providerName)) } // For local deployed models @@ -1613,7 +1735,7 @@ func (m *ModelProviderService) OCRFile(providerName, instanceName, modelName, us } newProviderInfo := providerInfo.ModelDriver.NewInstance(newURL) - var response *modelModule.OCRResponse + var response *modelModule.OCRFileResponse response, err = newProviderInfo.OCRFile(&modelName, content, url, apiConfig, ocrConfig) if err != nil { return nil, common.CodeServerError, err @@ -1628,6 +1750,116 @@ func (m *ModelProviderService) OCRFile(providerName, instanceName, modelName, us return nil, common.CodeServerError, errors.New("model is disabled") } +func (m *ModelProviderService) ParseFile(providerName, instanceName, modelName, userID string, content []byte, url *string, apiConfig *modelModule.APIConfig, parseFileConfig *modelModule.ParseFileConfig) (*modelModule.ParseFileResponse, common.ErrorCode, error) { + if apiConfig == nil { + apiConfig = &modelModule.APIConfig{} + } + if parseFileConfig == nil { + parseFileConfig = &modelModule.ParseFileConfig{} + } + + // Get tenant ID from user + tenants, err := m.userTenantDAO.GetByUserIDAndRole(userID, "owner") + if err != nil { + return nil, common.CodeServerError, err + } + + if len(tenants) == 0 { + return nil, common.CodeNotFound, errors.New("user has no tenants") + } + + tenantID := tenants[0].TenantID + + // Check if provider exists + provider, err := m.modelProviderDAO.GetByTenantIDAndProviderName(tenantID, providerName) + if err != nil { + return nil, common.CodeServerError, err + } + + instance, err := m.modelInstanceDAO.GetByProviderIDAndInstanceName(provider.ID, instanceName) + if err != nil { + return nil, common.CodeServerError, err + } + + modelInfo, err := m.modelDAO.GetModelByProviderIDAndInstanceIDAndModelName(provider.ID, instance.ID, modelName) + if err != nil { + providerInfo := dao.GetModelProviderManager().FindProvider(providerName) + if providerInfo == nil { + return nil, common.CodeNotFound, errors.New("provider not found") + } + + var model *entity.Model = nil + model, err = dao.GetModelProviderManager().GetModelByName(providerName, modelName) + if err != nil { + return nil, common.CodeNotFound, errors.New(fmt.Sprintf("provider %s model %s not found", providerName, modelName)) + } + + if !model.ModelTypeMap["doc_parse"] { + return nil, common.CodeNotFound, errors.New(fmt.Sprintf("provider %s model %s is not a Document Parse model", providerName, modelName)) + } + + var extra map[string]string + err = json.Unmarshal([]byte(instance.Extra), &extra) + if err != nil { + return nil, common.CodeServerError, err + } + + region := extra["region"] + apiConfig.Region = ®ion + apiConfig.ApiKey = &instance.APIKey + + var response *modelModule.ParseFileResponse + response, err = providerInfo.ModelDriver.ParseFile(&modelName, content, url, apiConfig, parseFileConfig) + if err != nil { + return nil, common.CodeServerError, err + } + if response == nil { + return nil, common.CodeServerError, errors.New("empty chat response") + } + + return response, common.CodeSuccess, nil + } + + if modelInfo.Status == "active" { + if modelInfo.ModelType != "doc_parse" { + return nil, common.CodeServerError, errors.New(fmt.Sprintf("expect model %s@%s is a Document Parse model", modelName, providerName)) + } + // For local deployed models + providerInfo := dao.GetModelProviderManager().FindProvider(providerName) + if providerInfo == nil { + return nil, common.CodeNotFound, errors.New("provider not found") + } + + var extra map[string]string + err = json.Unmarshal([]byte(instance.Extra), &extra) + if err != nil { + return nil, common.CodeServerError, err + } + + region := extra["region"] + apiConfig.Region = ®ion + apiConfig.ApiKey = &instance.APIKey + + newURL := map[string]string{ + region: extra["base_url"], + } + newProviderInfo := providerInfo.ModelDriver.NewInstance(newURL) + + var response *modelModule.ParseFileResponse + response, err = newProviderInfo.ParseFile(&modelName, content, url, apiConfig, parseFileConfig) + if err != nil { + return nil, common.CodeServerError, err + } + if response == nil { + return nil, common.CodeServerError, errors.New("empty chat response") + } + + return response, common.CodeSuccess, nil + } + + return nil, common.CodeServerError, errors.New("model is disabled") +} + // GetEmbeddingModel returns an EmbeddingModel wrapper for the given tenant func (m *ModelProviderService) GetEmbeddingModel(tenantID, compositeModelName string) (*modelModule.EmbeddingModel, error) { driver, modelName, apiConfig, maxTokens, err := m.getModelConfig(tenantID, compositeModelName) diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index f3d6456c47..32a3d5bfd6 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -192,8 +192,7 @@ export default { listAgentTemplate: `${restAPIv1}/agents/templates`, listAgents: `${restAPIv1}/agents`, listAgentTags: `${restAPIv1}/agents/tags`, - updateAgentTags: (agentId: string) => - `${restAPIv1}/agents/${agentId}/tags`, + updateAgentTags: (agentId: string) => `${restAPIv1}/agents/${agentId}/tags`, createAgent: `${restAPIv1}/agents`, updateAgent: (agentId: string) => `${restAPIv1}/agents/${agentId}`, deleteAgent: (agentId: string) => `${restAPIv1}/agents/${agentId}`,