Conversation
|
adfasd@xLow:~/.config/qmd$ qmd embed -c xyoutuber Embedding error: Error: OpenAI-compatible request failed (500 Internal Server Error): {"error":{"code":500,"message":"[json.exception.parse_error.101] parse error at line 1, column 53965: syntax error while parsing value - invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF; last read: '"title: Session: 2026-04-10 08:00:34 UTC | text: \udcca'","type":"server_error"}} ✓ Done! Embedded 0 chunks from 290 documents in 9s adfasd@xLow:~/.config/qmd$ qmd embed -c xyoutuber QMD Warning: no GPU acceleration, running on CPU (slow). Run 'qmd status' for details. |
|
Hi @loopyd — opened #705 consolidating the OpenAI-compatible-backend effort. It builds on #629's per-operation |
…tion/rerank Cribs the approach of unmitted upstream PR tobi#619 (tobi/qmd), re-implemented on top of current main (v2.6.3) rather than merged, since the PR is based on v2.1.0 and merging it wholesale would drop 123 commits of concurrency/CJK/ busy-timeout fixes. Scoped to what weaver needs: remote embeddings via OpenRouter (google/gemini-embedding-2, 3072-dim); generation/expansion/rerank are implemented for completeness and degrade gracefully. - Widen the LLM interface into the shared backend contract (embedModelName, embedBatch, tokenize/detokenize, getDeviceInfo?) that both backends satisfy. - Add OpenAICompatibleLLM: POST {baseUrl}/embeddings|chat/completions|rerank, batches embeddings, maps by response index (falls back to array position), heuristic whitespace tokenizer for chunk sizing, oversized-rerank recovery. - Add createLLM() factory + resolveLlmProvider(); route the default singleton and per-store instance through it. Backend selected by config.llm.provider / llm.baseUrl or QMD_LLM_PROVIDER / QMD_OPENAI_BASE_URL; llama.cpp stays default. - Config: add llm.{provider,baseUrl,apiKey} to index.yml schema. - getDefaultLlamaCpp() retained as a soft compat alias over getDefaultLLM(). - Tests: OpenAICompatibleLLM unit tests (fully mocked); full suite green (892 passed, 73 model-requiring skipped). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Problem
QMD currently assumes a local
llama.cppstyle setup for generation, embeddings, and reranking which is baked in. This prevents any and all user customization to llama cpp (such as for example, using TheTom's turboquant fork...) and doesn't integrate well with existing homelab servers as it tries to run it all locally on the machine.That makes it harder to use QMD with a local OpenAI-compatible server setup, such as
llama-swapwhich is what I tested this PR with. Even when the server already exposes the same models through/v1/chat/completions,/v1/embeddings, and/v1/rerank, why can't we? Now we can!Solution
This PR adds an OpenAI-compatible backend alongside the existing llama cpp one that lets QMD talk to a local compatible server instead of requiring direct local model access. Meaning now you can run this on your laptop while your homelab does the tensor crunching!
It also makes the CLI and store paths respect configured model aliases, so users can route QMD generation, embedding, and reranking through named server-side models (ex:
qmd-generate,qmd-embed, andqmd-rerank) which I set my server up with for testing this PR.What's Changed?
llm.providerllm.baseUrlllm.apiKeyqmd embedso it uses the configured embedding alias instead of forcing the built-in default model name.Testing
Automated
npx vitest run test/llm.test.ts -t "recovers from oversized rerank requests by splitting and truncating"npx vitest run test/llm.test.ts -t "rerank maps remote indices back to source files"Manual
You can test this with
llama-swapor any server that exposes OpenAI-compatible chat, embedding, and rerank endpoints.Option A: Use llama-swap
qmd-generateqmd-embedqmd-rerankPOST /v1/chat/completionsPOST /v1/embeddingsPOST /v1/rerankOption B: Roll your own compatible server
QMD config example
Create or update your QMD config:
Verify the flow
qmd --index my-index query "How do I unpack EMI archives?" -n 3 --jsonConfirm the server receives requests for:
If your reranker has tighter request limits, verify the query still succeeds and that rerank requests continue after the first oversized split when needed.