Conversation
|
Thanks for this — it's obvious you ran it against a real self-hosted stack rather than writing to the docs, and the I validated it against current
Four things before this can land, roughly in order of how much they matter: 1. The non-hosted 2. 3. A plaintext override silently ships the API key. 4. Two mechanical items, same as the other PR: please rebase (this is currently conflicting), and drop the None of the four are objections to the idea. Pointing an OpenAI-compatible provider at LM Studio or vLLM is exactly what a self-hoster should be able to do, and the incompatibility handling is the part most people would have skipped. |
|
Rebased onto 1. Evidence. Verbatim error bodies, 2026-08-25/26, Caura 2.30.0 as shipped,
All 44 chat calls in one write sweep returned one of those two, then LM Studio 0.3.x serving So the Anthropic and OpenRouter defaults were not receiving a working 2. Optional fields. You are right that "required" changed model behaviour. Now: a property the source schema left optional is made nullable on the wire ( 3. Plaintext override. One warning at construction when the base URL is 4. Local run at |
a7f321b to
002c618
Compare
|
Rebased onto Local run at |
002c618 to
5b2993e
Compare
The chat path (dedup judge, enrichment, contradiction detection, entity
extraction) could only reach each provider's hosted URL, and it sent a
response_format that only hosted OpenAI accepts. Both gaps meant a
self-hosted model or Caura's own Anthropic setting silently ran on the
fake fallback provider.
Base URLs: OPENAI_CHAT_BASE_URL, ANTHROPIC_CHAT_BASE_URL, and
OPENROUTER_CHAT_BASE_URL now come from the environment, with the hosted
URLs as defaults, the same way ANTHROPIC_DEFAULT_MODEL is read. The
constant OPENAI_HOSTED_CHAT_BASE_URL keeps the literal hosted URL so the
provider can tell whether it is talking to api.openai.com after an
override. The stale comment that described a tenant-config URL swap is
replaced; only the provider name was ever swapped.
Response format: hosted OpenAI keeps json_object without a schema and a
non-strict json_schema with one, so nothing changes there. Every other
base URL gets no response_format without a schema, because LM Studio
rejects json_object ("must be 'json_schema' or 'text'") and Anthropic's
compatible endpoint rejects it too ("Input should be 'json_schema'").
With a schema it gets strict: true and a closed schema, which Anthropic
requires ("strict: Input should be True", and every object needs
additionalProperties: false). A helper closes each object and drops
default and title. A Markdown code fence around the reply is stripped
before json.loads, because a model with no response_format usually adds
one.
Observed against Caura 2.30.0 on 2026-08-25: with
ENTITY_EXTRACTION_PROVIDER=anthropic and a funded key, all 44 chat calls
in one write sweep returned HTTP 400 and fell back to the fake provider.
Signed-off-by: zznate <zznate.m@gmail.com>
…hosted by host Review follow-ups on the chat-path change. Strict mode requires every property, which changed what the model must emit: a field the source schema left optional had to be sent, so the model could invent a value where it used to omit the key. _strict_schema now makes such a field nullable on the wire, and _drop_optional_nulls removes a null for an originally optional, non-nullable key after parsing. Callers see the absent-or-present shape they saw before; a null for a field the source schema itself made nullable is kept as sent. Tests run ExtractedGraph and EnrichmentResult through both helpers and validate the result with the Pydantic models. _is_hosted_openai decides by host, so http://api.openai.com/v1 and a trailing slash count as hosted; the docstring says that a proxy on another host is non-hosted on purpose, because the shapes are chosen for what the server accepts and hosted OpenAI takes the strict shapes too. A plain-http chat base URL to a non-loopback host now logs one warning at construction: the provider key travels in the clear on every call. Operator configuration, so a warning and not a refusal. Signed-off-by: zznate <zznate.m@gmail.com>
Signed-off-by: Eldad Caura <eldad@caura.ai>
5b2993e to
0d6e95c
Compare
|
Maintainer update pushed at 0d6e95c. I rebased the two original signed contributor commits onto current main unchanged, then added a separate signed one-line fix replacing the Ruff C420 comprehension with dict.fromkeys. Verification: Ruff check passed for tests/, Ruff format --check passed for tests/ (395 files), the 30 focused compatibility tests passed, and both naming gates passed. Waiting for exact-head CI before retriggering review. |
SummaryThis PR adds self-hosted/OpenAI-compatible chat LLM support by making the three chat base URLs environment-overridable and by branching Medium/Low IssuesOpenRouter is reclassified into the "non-hosted" branch without verification, changing existing production behaviorSeverity: Medium 🤖 Claude Code PromptStale docstring still claims
|
Signed-off-by: Eldad Caura <eldad@caura.ai>
SummaryThe PR adds environment-var overrides for chat-provider base URLs and teaches Medium IssuesPlaintext-off-host warning logs on every LLM call, not onceSeverity: Medium 🤖 Claude Code Prompt
|
Summary
Let the chat LLM path (dedup judge, enrichment, contradiction detection, entity extraction) use any OpenAI-compatible endpoint. The three chat base URLs now come from the environment, and
complete_jsonsends aresponse_formatthe endpoint accepts. Hosted OpenAI behaviour does not change.Related Issue
Closes #992
Type of Change
How Has This Been Tested?
New tests in
tests/test_llm_openai_compatible_json_mode.pybuild the real provider and swap only its transport, the waytest_llm_provider_sdk_retries.pydoes. They pin:json_objectwithout a schema and a non-strictjson_schemawith one;response_formatwithout a schema, andstrict: truewith a closed schema when one is given, including nested$defs;defaultandtitle;*_CHAT_BASE_URLvariable wins over the default, checked in a fresh interpreter because the constants are read at import time.Verified live against 2.30.0 plus this change, built with
core-api/Dockerfile:ENTITY_EXTRACTION_PROVIDER=anthropicwithclaude-haiku-4-5-20251001: all chat calls succeed. The dedup judge rejects a paraphrase in the judge band, admits a refinement and a negation at confidence 0.90, and the entity extraction calls complete through the strict schema with no fallback warning in the log. On the shipped build every one of these calls returned HTTP 400 and fell back to the fake provider.OpenAILLMProvider.complete_jsonwithbase_url=http://localhost:1234/v1against LM Studio servingqwen2.5-coder-7b-instruct: the no-schema path returns a parsed verdict, where the shipped build was rejected with'response_format.type' must be 'json_schema' or 'text'.Checklist
ruff checkandruff format --checkpassmypypassespytestpasses locallyCHANGELOG.mdunder theUnreleasedsection (if user-facing)Additional Notes
OPENAI_HOSTED_CHAT_BASE_URLkeeps the literal hosted URL so the provider can tell, after an override, whether it is talking toapi.openai.com. That is the one endpoint that acceptsjson_object, so it keeps the old shapes and nothing changes for existing deployments.strict: trueand rejects any object withoutadditionalProperties: false. Pydantic-generated schemas leave both open for fields with defaults. The helper closes them on a copy; the models are untouched, and the client-side Pydantic parse stays the real guard, as the docstring already says.CHANGELOG.mdhad noUnreleasedsection; this adds one above 2.30.0.