feat(kg): support local/OpenAI-compatible LLM endpoints - #816
Conversation
|
All contributors have signed the CLA ✍️ ✅ |
|
👋 Welcome, @bferanmi806-sketch, and thanks for opening your first PR on AnythingMCP! A few quick pointers:
Someone from the core team will look at this within ~48h. If you don't hear back, please ping us in Discussions / Q&A. ⭐ While you wait — if you find AnythingMCP useful, a star helps others discover it. |
) When KG_LLM_BASE_URL is set, the KG enrichment/skill LLM path talks to that endpoint (Ollama, LM Studio, ...) using KG_LLM_MODEL. KG_LLM_API_KEY is optional and only sent as Authorization when present; response_format is never sent on this path — the JSON instruction plus shared parsing apply, and unusable replies are logged (model + endpoint) and skipped as an empty result instead of crashing the flow. Hosted OpenAI / OpenRouter / Anthropic behaviour and Anthropic-only batch mode are unchanged.
0c4798a to
cf4084e
Compare
|
recheck |
|
I have read the CLA Document and I hereby sign the CLA |
keysersoft
left a comment
There was a problem hiding this comment.
Thanks @bferanmi806-sketch, this is close. The hosted paths are byte-identical, I ran the KG and cloud cron suites locally (128 passing, tsc clean), and since the base URL only comes from the instance env there's no new SSRF surface.
One thing has to change before I merge. Returning { json: {} } on a bad reply doesn't skip the pass, because the callers treat it as a valid empty answer:
KgLlmService.enrich()hands it toapplyEnrichResult(), which storeskg_llm_hash. The next run sees an unchanged graph and returnsskipped, so one garbled reply blocks enrichment until someone forces a rerun.applyConnectorResult()andgenerateForServer()delete the pending skill suggestions before inserting the new ones, so an empty result wipes the pending list.
consolidate() already ignores an empty result, so that one is fine.
Could you make the skip explicit? For example return { json: null, skipped: true } (or throw a small LlmUnusableReplyError) on the custom path, and have enrich(), generateForConnectors() and generateForServer() return early without touching the hash or the pending suggestions. A test for the enrich case and one for the skills case would be great. While you're there, please log new URL(base).origin instead of the full base, in case someone puts credentials in the URL.
Two smaller things on the docs:
- The block in
docs/knowledge-graph.mdis meant to be copied, andKG_LLM_BASE_URL=http://localhost:11434/v1is uncommented there. Someone pasting it with an OpenAI key would end up on localhost without noticing. Please comment it out like you did in.env.example. - Most self-hosters run us in Docker, where
localhostis the container itself. Please add a line abouthttp://host.docker.internal:11434/v1(on Linux that needsextra_hosts: ["host.docker.internal:host-gateway"]), or the service name when Ollama runs in the same compose project. Also,docker-compose.ymldoesn't pass anyKG_LLM_*variable to the app container today. If you're up for it, addKG_LLM_ENABLED,KG_LLM_PROVIDER,KG_LLM_MODEL,KG_LLM_BASE_URLandKG_LLM_API_KEYthere with empty defaults. If not, I'll do it in a follow-up.
Thanks for being upfront about the llama.cpp harness, that's fine with me.
|
@keysersoft addressed in 3125f79, same branch:
No replacement PR opened, nothing merged. |
…; docker/docs wiring (HelpCode-ai#598 review) Custom path now resolves { json: null, skipped: true } instead of an empty answer, and enrich()/generateForConnectors()/generateForServer() return early: no kg_llm_hash write, no pending-suggestion replacement. Endpoint logging uses new URL(base).origin only. Docs block comments out the localhost URL and covers Docker networking; docker-compose.yml passes the five KG_LLM_* vars through with empty defaults.
3125f79 to
d8138af
Compare
|
recheck |
keysersoft
left a comment
There was a problem hiding this comment.
Thanks @bferanmi806-sketch, that was quick and it's exactly what I asked for. I ran the KG and cloud cron suites on your branch (132 passing, tsc clean) and the skip tests read well.
I pushed one small commit on top so we don't need another round: the Docker note had split the env block in docs/knowledge-graph.md, so everything after it rendered as headings. It's back in one code fence with the note below it. I also added OPENAI_API_KEY, OPENROUTER_API_KEY and ANTHROPIC_API_KEY to docker-compose.yml, otherwise the hosted path still couldn't get its key into the container.
CI is approved and running. I'll merge once it's green.
|
Thanks @keysersoft, really appreciate the review and the follow-up fixes. Glad the skip behavior landed the way you wanted. I enjoyed working through this one. If there are other issues around the KG, local model support, or anything else you think would be a good fit, feel free to tag or assign me happy to keep contributing. |
|
Merged, thanks again @bferanmi806-sketch. It'll be in the next release. |
Fixes #598. @keysersoft — implemented per your approved contract, plus the review follow-ups below.
Contract compliance:
KG_LLM_BASE_URL(notAI_BASE_URL); optionalKG_LLM_API_KEY(notAI_*); keeps usingKG_LLM_MODEL.Authorization: Bearer …only when a key exists (custom path); hosted paths byte-identical.response_formaton the custom endpoint — JSON instruction + existing parsing instead.{ json: null, skipped: true };enrich()/generateForConnectors()/generateForServer()return early with no hash write and no suggestion replacement, so the next run retries normally.consolidate()unchanged and safe (json?.skills→ early return).new URL(base).originonly (safe fallback when unparseable).Tests:
llm-client.spec.ts(9 tests), newkg-llm.service.spec.ts(hash untouched on skip + retry stores normally),kg-skill.service.spec.tsadditions (pending suggestions untouched on skip, still replaced on usable reply, consolidate untouched on skip). Focused suites 17/17; broadersrc/knowledge-graph+src/ee/cloud132/132;eslintclean; zerotscerrors in touched files (one pre-existingcookie-parsertypes error repo-wide).Docs/Docker:
docs/knowledge-graph.mdblock now comments out the localhost URL, explains Dockerlocalhost, documentshost.docker.internal:11434+ Linuxextra_hosts+ compose service-name option.docker-compose.ymlpasses all fiveKG_LLM_*vars with empty defaults (docker compose configvalidates).docker-compose.quickstart.ymlintentionally untouched (minimal eval stack, carries no KG vars at all).Real local run (honest harness note: the Ollama binary download is blocked in this sandbox, so this ran Qwen2.5-0.5B-Instruct via llama.cpp behind a minimal
/v1/chat/completionsfront — the identical request/response contract Ollama serves):{provider:custom, model:qwen2.5, apiKey:'', baseUrl:http://localhost:11435/v1}response_format=null auth=absent{relationships:[{from:e0, to:e1, kind:same_identity, confidence:0.9, reason:e0 is a person and e1 is a customer}]}Happy to sign the CLA when the bot asks.