refactor(backend): speak only OpenAI-compat /v1 to the local backend
Three native-API touchpoints converted — steward /api/generate to /v1/chat/completions, embeddings /api/embeddings to /v1/embeddings, health /api/tags to /v1/models — so the backend behind OLLAMA_HOST is swappable by env alone. This makes the serving plan's "tatlock needs zero changes" claim true for the llama-server cutover and for forge after it. EMBEDDING_HOST (default: OLLAMA_HOST) lets gen and embed point at different servers, which the boilerroom stack needs. The dead OllamaClient goes with it: a native-API client nothing imported, whose presence would make the post-cutover "no native endpoints" grep lie. Contract tests rewritten to mirror the new requests and extended with the embeddings shape (dim must match the configured Qdrant dimension). All pass against live Ollama's /v1 — deployable before any cutover. Both new assertions mutation-checked via env overrides (bogus model, wrong dim: each fails). The Anthropic contract now skips on 401: the configured key is deliberately revoked per workspace D-11, which is "fallback disabled", not a boundary break. Embedding continuity across backends was measured separately: same nomic bytes, cosine 1.0000. 663 unit tests pass; ruff clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -156,24 +156,26 @@ class StewardAgent:
|
||||
return response.content[0].text.strip()
|
||||
|
||||
async def _call_ollama(self, prompt: str) -> str:
|
||||
"""Call Ollama API directly for plain text generation."""
|
||||
"""Call the local backend's OpenAI-compatible chat endpoint.
|
||||
|
||||
Plain /v1/chat/completions with sampling params, so any
|
||||
OpenAI-compatible server (Ollama, llama-server) can sit behind
|
||||
OLLAMA_HOST without this method knowing which.
|
||||
"""
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
response = await client.post(
|
||||
f"{self.ollama_host}/api/generate",
|
||||
f"{self.ollama_host}/v1/chat/completions",
|
||||
json={
|
||||
"model": self.ollama_model,
|
||||
"prompt": prompt,
|
||||
"stream": False,
|
||||
"options": {
|
||||
"temperature": 0.3, # Lower = more consistent
|
||||
"top_p": 0.9,
|
||||
},
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"temperature": 0.3, # Lower = more consistent
|
||||
"top_p": 0.9,
|
||||
},
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
return result["response"].strip()
|
||||
return result["choices"][0]["message"]["content"].strip()
|
||||
|
||||
async def analyze(self, query: str, conversation_history: list[dict] | None = None) -> str:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user