mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-05-28 19:53:06 +08:00
### What problem does this PR solve? Add a Go driver for LongCat (Meituan, https://longcat.chat), one of the unchecked providers on the umbrella tracking issue #14736. LongCat exposes an OpenAI-compatible REST API at `https://api.longcat.chat/openai/v1` with three public chat models including `LongCat-Flash-Thinking`, a reasoning model that returns chain-of-thought in `reasoning_content` (OpenAI o-series shape). Until this PR, a tenant who configured `longcat` as a model provider in the Go layer fell through to the default branch of `internal/entity/models/factory.go` and got the dummy driver. ### What this PR includes - New `internal/entity/models/longcat.go` with a `LongCatModel` implementing the `ModelDriver` interface. - New `conf/models/longcat.json` with the 3 public chat models (Flash-Chat, Flash-Lite, Flash-Thinking) and `url_suffix` for `chat` and `models`. - `factory.go`: route `"longcat"` to `NewLongCatModel`. Method coverage: - `ChatWithMessages`: `POST /openai/v1/chat/completions`, non-streaming - `ChatStreamlyWithSender`: SSE stream against the same endpoint - `ListModels` / `CheckConnection`: `GET /openai/v1/models` - **Reasoning extraction**: `message.reasoning_content` (non-stream) and `delta.reasoning_content` (stream) flow into `ChatResponse.ReasonContent` / the sender's second arg. Matches the OpenAI o-series convention also used by kimi-k2.6 and DeepSeek-R1. - **`reasoning_effort` propagation**: `ChatConfig.Effort` → request body `reasoning_effort` (LongCat-Flash-Thinking honors it; non-reasoning models ignore it). - `Embed` / `Rerank` / `Balance` / `TranscribeAudio` / `AudioSpeech` / `OCRFile` return `"no such method"` (LongCat does not expose any of these surfaces). No interface change. No new dependencies. ### How was this tested? **21 unit tests** in `internal/entity/models/longcat_test.go` — all pass: ``` $ go test -vet=off -run TestLongCat -count=1 -v ./internal/entity/models/... === RUN TestLongCatName --- PASS: TestLongCatName (0.00s) === RUN TestLongCatChatHappyPath --- PASS: TestLongCatChatHappyPath (0.00s) === RUN TestLongCatChatExtractsReasoningContent --- PASS: TestLongCatChatExtractsReasoningContent (0.00s) === RUN TestLongCatChatPropagatesReasoningEffort --- PASS: TestLongCatChatPropagatesReasoningEffort (0.00s) === RUN TestLongCatChatOmitsReasoningEffortWhenUnset --- PASS: TestLongCatChatOmitsReasoningEffortWhenUnset (0.00s) === RUN TestLongCatChatRequiresAPIKey --- PASS: TestLongCatChatRequiresAPIKey (0.00s) === RUN TestLongCatChatRequiresMessages --- PASS: TestLongCatChatRequiresMessages (0.00s) === RUN TestLongCatChatRejectsHTTPError --- PASS: TestLongCatChatRejectsHTTPError (0.00s) === RUN TestLongCatStreamHappyPath --- PASS: TestLongCatStreamHappyPath (0.00s) === RUN TestLongCatStreamExtractsReasoningContent --- PASS: TestLongCatStreamExtractsReasoningContent (0.00s) === RUN TestLongCatStreamRejectsExplicitFalse --- PASS: TestLongCatStreamRejectsExplicitFalse (0.00s) === RUN TestLongCatStreamRequiresSender --- PASS: TestLongCatStreamRequiresSender (0.00s) === RUN TestLongCatStreamFailsWithoutTerminal --- PASS: TestLongCatStreamFailsWithoutTerminal (0.00s) === RUN TestLongCatListModelsHappyPath --- PASS: TestLongCatListModelsHappyPath (0.00s) === RUN TestLongCatListModelsRequiresAPIKey --- PASS: TestLongCatListModelsRequiresAPIKey (0.00s) === RUN TestLongCatCheckConnectionDelegatesToListModels --- PASS: TestLongCatCheckConnectionDelegatesToListModels (0.00s) === RUN TestLongCatEmbedReturnsNoSuchMethod --- PASS: TestLongCatEmbedReturnsNoSuchMethod (0.00s) === RUN TestLongCatRerankReturnsNoSuchMethod --- PASS: TestLongCatRerankReturnsNoSuchMethod (0.00s) === RUN TestLongCatBalanceReturnsNoSuchMethod --- PASS: TestLongCatBalanceReturnsNoSuchMethod (0.00s) === RUN TestLongCatAudioOCRReturnNoSuchMethod --- PASS: TestLongCatAudioOCRReturnNoSuchMethod (0.00s) PASS ok ragflow/internal/entity/models 0.020s ``` `go build ./internal/entity/models/...` exits 0 on go 1.25. **Live integration test** against `api.longcat.chat`: ``` === RUN TestLongCatLiveSmoke [OK] Name() = "longcat" [OK] CheckConnection [OK] ListModels: 5 models -> [LongCat-Flash-Lite LongCat-Flash-Chat LongCat-Flash-Thinking-2601 LongCat-Flash-Omni-2603 LongCat-2.0-Preview] [OK] Chat (Flash-Chat) answer="Got it! Let me know if you" reason="" [OK] Chat (Flash-Thinking) answer len=443 head="To find 15 % of 80, follow these steps:\n\n1. **Convert the percentage to a frac..." ReasonContent len=557 head="The user asks: \"15% of 80?\" They want step by step reasoning and final answer in \\boxed{}. So we need to compute 15% of ..." [OK] Stream content: 78 chunks, 351 chars [OK] Stream reasoning: 107 chunks, 537 chars [OK] Balance returns longcat, no such method [OK] Embed returns longcat, no such method [OK] Rerank returns longcat, no such method LONGCAT LIVE SMOKE PASSED --- PASS: TestLongCatLiveSmoke (31.01s) ``` What the live run proves on the wire: - Auth header (`Bearer <key>`) is accepted by `api.longcat.chat`. - `/openai/v1/models` parser handles the real 5-model response (note: live API returns versioned aliases `LongCat-Flash-Thinking-2601`, `LongCat-Flash-Omni-2603`, `LongCat-2.0-Preview` plus the un-versioned `LongCat-Flash-Chat` and `LongCat-Flash-Lite`). - Non-stream chat against `LongCat-Flash-Chat`: visible answer parses correctly, `ReasonContent` correctly empty. - Non-stream chat against `LongCat-Flash-Thinking`: 443-char answer flows into `Answer`, 557-char chain-of-thought flows into `ReasonContent` via the new `message.reasoning_content` extraction. - Streaming chat against `LongCat-Flash-Thinking`: 107 reasoning chunks (537 chars) reach the sender's second arg via `delta.reasoning_content`; 78 content chunks (351 chars) reach the first arg. Before this code, the reasoning chunks would have been silently dropped. - All sentinel methods (Balance, Embed, Rerank, audio/OCR) return the documented `"no such method"` strings. ### Note on PR history This branch was previously named for LocalAI work which is now consolidated into PR #14813. The branch was reset to `upstream/main` and rebuilt for LongCat. The diff against `main` is a clean +969 lines across 4 files. ### Type of change - [x] New Feature (non-breaking change which adds functionality) Tracking: #14736 --------- Co-authored-by: Jin Hai <haijin.chn@gmail.com>
48 lines
816 B
JSON
48 lines
816 B
JSON
{
|
|
"name": "LongCat",
|
|
"url": {
|
|
"default": "https://api.longcat.chat"
|
|
},
|
|
"url_suffix": {
|
|
"chat": "openai/v1/chat/completions"
|
|
},
|
|
"class": "longcat",
|
|
"models": [
|
|
{
|
|
"name": "LongCat-Flash-Chat",
|
|
"max_tokens": 131072,
|
|
"model_types": [
|
|
"chat"
|
|
]
|
|
},
|
|
{
|
|
"name": "LongCat-Flash-Lite",
|
|
"max_tokens": 131072,
|
|
"model_types": [
|
|
"chat"
|
|
]
|
|
},
|
|
{
|
|
"name": "LongCat-Flash-Thinking-2601",
|
|
"max_tokens": 131072,
|
|
"model_types": [
|
|
"chat"
|
|
]
|
|
},
|
|
{
|
|
"name": "LongCat-Flash-Omni-2603",
|
|
"max_tokens": 131072,
|
|
"model_types": [
|
|
"chat"
|
|
]
|
|
},
|
|
{
|
|
"name": "LongCat-2.0-Preview",
|
|
"max_tokens": 131072,
|
|
"model_types": [
|
|
"chat"
|
|
]
|
|
}
|
|
]
|
|
}
|