From b5389fce455a28aee85746a9dd2c06716d9775ca Mon Sep 17 00:00:00 2001 From: flemming-it Date: Sat, 11 Jul 2026 23:12:39 +0200 Subject: [PATCH 1/2] chore(build): pin chain-module-sdk v0.3.0 and adopt #[chain_module] SDK 0.3.0 drops the fai-legacy #[fai_module] alias; pin the tag and flip the attribute, rebuilt module.wasm accordingly. Signed-off-by: flemming-it --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- module.wasm | Bin 301389 -> 302110 bytes src/lib.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dcff588..45c991c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,8 +40,8 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chain-module-sdk" -version = "0.1.2" -source = "git+https://git.flemming.ai/fai/chain-module-sdk-rust.git?branch=main#98e98e9371f7409560a1ef08bc0923d9a2506449" +version = "0.3.0" +source = "git+https://git.flemming.ai/fai/chain-module-sdk-rust.git?tag=v0.3.0#a4415ada7e76aac5e3aabf3e4c4311ffc348d8d0" dependencies = [ "chain-module-sdk-macros", "serde", @@ -52,8 +52,8 @@ dependencies = [ [[package]] name = "chain-module-sdk-macros" -version = "0.1.2" -source = "git+https://git.flemming.ai/fai/chain-module-sdk-rust.git?branch=main#98e98e9371f7409560a1ef08bc0923d9a2506449" +version = "0.3.0" +source = "git+https://git.flemming.ai/fai/chain-module-sdk-rust.git?tag=v0.3.0#a4415ada7e76aac5e3aabf3e4c4311ffc348d8d0" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 72db65b..9be0a66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ description = "F∆I module providing llm.chat" crate-type = ["cdylib", "rlib"] [dependencies] -chain-module-sdk = { git = "https://git.flemming.ai/fai/chain-module-sdk-rust.git", branch = "main" } +chain-module-sdk = { git = "https://git.flemming.ai/fai/chain-module-sdk-rust.git", tag = "v0.3.0" } serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "2" diff --git a/module.wasm b/module.wasm index a531ccdb41c67da31dcb9f07498e331a141b8f76..cca97496ba1b52204c995ad28e04b0ab0d31ba27 100644 GIT binary patch delta 53169 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 delta 52477 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 diff --git a/src/lib.rs b/src/lib.rs index 54d729d..d333fa8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ mod llm; use chain_module_sdk::prelude::*; -#[fai_module] +#[chain_module] pub fn invoke(_ctx: Context, inputs: Inputs) -> Result { let prompt = inputs.require_text("prompt")?.to_string(); let endpoint = inputs.require_text("endpoint")?.to_string(); From 305ab1cecd25de07fc4938e295e6d4501f9d8488 Mon Sep 17 00:00:00 2001 From: flemming-it Date: Sat, 11 Jul 2026 23:19:32 +0200 Subject: [PATCH 2/2] feat: OpenAI and Anthropic wire-format adapters via new api input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New optional input api: ollama|openai|anthropic (default ollama — existing flows unchanged). - openai: OpenAI Chat Completions format (/v1/chat/completions), Bearer auth — targets vLLM and compatible self-hosted servers. - anthropic: Messages API (/v1/messages), x-api-key + anthropic-version headers, top-level system field, mandatory max_tokens (fixed 4096). - Audit outputs unchanged: model_endpoint/model_name always set; model_digest stays Ollama-only (no digest API on openai/anthropic, field is left empty rather than fabricated). - api_key is only ever placed in auth headers; never in outputs, errors, or audit events (verified against the event log). - No streaming, no tool calls. Bump module + capability version to 0.2.0. Wire-format unit tests for request serialization and response parsing against fixed JSON fixtures; live smoke green on the ollama path and on the openai path against an OpenAI-compatible local endpoint. Signed-off-by: flemming-it --- Cargo.lock | 2 +- Cargo.toml | 2 +- MODULE.de.md | 80 ++++++-- MODULE.md | 76 ++++++-- README.md | 10 +- module.wasm | Bin 302110 -> 312033 bytes module.yaml | 39 +++- src/lib.rs | 78 +++++--- src/llm.rs | 511 +++++++++++++++++++++++++++++++++++++++++++++------ 9 files changed, 662 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45c991c..462390b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,7 +138,7 @@ checksum = "6cc46bac87ef8093eed6f272babb833b6443374399985ac8ed28471ee0918545" [[package]] name = "llm_chat" -version = "0.1.0" +version = "0.2.0" dependencies = [ "chain-module-sdk", "serde", diff --git a/Cargo.toml b/Cargo.toml index 9be0a66..6a761ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "llm_chat" -version = "0.1.0" +version = "0.2.0" edition = "2024" license = "Apache-2.0" publish = false diff --git a/MODULE.de.md b/MODULE.de.md index 4d12be6..ab71365 100644 --- a/MODULE.de.md +++ b/MODULE.de.md @@ -1,23 +1,35 @@ # llm.chat -Generischer Ollama-kompatibler LLM-Chat-Adapter. Das -Baustein-Modul für jeden Flow, der eine LLM-Einzelantwort -braucht — klassifizieren, Felder extrahieren, umformulieren, -entscheiden — ohne einen eigenen HTTP-Client zu bauen. +Generischer LLM-Chat-Adapter. Das Baustein-Modul für jeden +Flow, der eine LLM-Einzelantwort braucht — klassifizieren, +Felder extrahieren, umformulieren, entscheiden — ohne einen +eigenen HTTP-Client zu bauen. Spricht drei Wire-Formate, +wählbar über die optionale Eingabe `api`: Ollama (Default), +OpenAI Chat Completions (OpenAI, vLLM, kompatible Server) und +die Anthropic Messages API. ## Capability -- `llm.chat@0.1.0` +- `llm.chat@0.2.0` ## Eingaben | Name | Typ | Beschreibung | | ---------------- | ---- | ------------------------------------------------------------------------- | | `prompt` | text | Der User-Prompt. | -| `endpoint` | text | Ollama-förmiger `/api/chat`-URL (z.B. `http://localhost:11434/api/chat`). | -| `model` | text | Modell-ID (z.B. `qwen2.5:14b`, `llama3.1:8b`). | -| `api_key` | text | Optionaler Bearer-Token für Cloud-Endpunkte. | +| `endpoint` | text | Chat-Endpunkt-URL passend zur gewählten `api` (siehe unten). | +| `model` | text | Modell-ID (z. B. `qwen2.5:14b`, `claude-fable-5`). | +| `api_key` | text | Optionaler API-Key. Erscheint nie in Ausgaben, Logs oder Events. | | `system_prompt` | text | Optionale System-Nachricht. Leer = Default des Modells. | +| `api` | text | Optionales Wire-Format: `ollama` (Default), `openai`, `anthropic`. | + +### Wire-Formate (`api`) + +| `api` | Endpunkt-Form | Auth-Header | Hinweise | +| ----------- | ----------------------------------------- | ---------------------------------- | ------------------------------------------------------------ | +| `ollama` | `http://localhost:11434/api/chat` | `Authorization: Bearer` (optional) | Verhalten wie v0.1.0, unverändert. Modell-Digest-Probe via `/api/show`. | +| `openai` | `https://.../v1/chat/completions` | `Authorization: Bearer` | OpenAI-Chat-Completions-Format — sprechen auch vLLM und die meisten selbst gehosteten Inferenz-Server. | +| `anthropic` | `https://api.anthropic.com/v1/messages` | `x-api-key` + `anthropic-version` | Messages API: `system` ist Top-Level-Feld; `max_tokens` ist Pflicht und steht fest auf 4096. | ## Ausgaben @@ -26,7 +38,7 @@ entscheiden — ohne einen eigenen HTTP-Client zu bauen. | `response` | text | Die Antwort des Assistants als Plain-Text. | | `model_endpoint` | text | URL, gegen die die Antwort erzeugt wurde. | | `model_name` | text | Modell-ID wie an die LLM-API gesendet. | -| `model_digest` | text | SHA-256-Digest des bedienenden Ollama-Modells. Leer bei Cloud-APIs. | +| `model_digest` | text | SHA-256-Digest des bedienenden Ollama-Modells. Leer bei `openai`/`anthropic` — diese APIs bieten keinen Digest; das Feld bleibt leer statt erfunden. | Zusammen beantworten die drei `model_*`-Ausgaben die Audit- Frage: „Welches genaue Modell hat diese Antwort erzeugt?" — @@ -43,8 +55,8 @@ permissions: - "net: api.anthropic.com" ``` -Loopback (lokales Ollama) per Default. Cloud-Endpunkte brauchen -einen Operator-Policy-Override in +Loopback (lokales Ollama / vLLM) per Default. Cloud-Endpunkte +brauchen einen Operator-Policy-Override in `~/.chain/config.yaml#security.max_permissions`. ## Warum dieses Modul statt Inline-HTTP @@ -61,23 +73,24 @@ Drei Gründe: `net:`-Liste. Ein hausgemachtes HTTP-Modul würde seine Endpunkte entweder verstecken oder bei jeder Installation eine frische Review-Oberfläche schaffen. -3. **Endpunkt-Portabilität.** Endpunkt + Modell sind Flow- - Inputs, keine Compile-Time-Konstanten. Derselbe Flow läuft - in der Entwicklung gegen `localhost:11434` und in Produktion - gegen ein Inferenz-Cluster — nur die Eingabe wechselt. +3. **Endpunkt-Portabilität.** Endpunkt, Modell und Wire-Format + sind Flow-Inputs, keine Compile-Time-Konstanten. Derselbe + Flow läuft in der Entwicklung gegen `localhost:11434` und + in Produktion gegen ein vLLM-Cluster — nur die Eingaben + wechseln. -## Grenzen in v0.1.0 +## Grenzen in v0.2.0 - Kein Streaming. Die ganze Antwort wird gepuffert, bevor der Output-Step feuert. - Keine Tool-Call- / Function-Call-Oberfläche. Ein Flow, der Tool-Use braucht, kombiniert mehrere `llm.chat`-Schritte mit Prompt-Engineering oder nutzt MCP via Bridge. -- Cloud-Provider-Adapter für OpenAI und Anthropic kommen erst, - wenn ein Flow sie braucht. Heute ist das Ollama-Wire-Format - das einzige Ziel. +- Anthropic-`max_tokens` steht fest auf 4096 (die API verlangt + das Feld; eine konfigurierbare Eingabe kommt, sobald ein + Flow sie braucht). -## Beispiel-Flow +## Beispiel-Flows ```yaml name: classify-incoming @@ -98,6 +111,33 @@ outputs: audit_model: $classify.model_digest ``` +Gegen einen vLLM-Server (OpenAI-kompatibel): + +```yaml +steps: + - id: classify + use: llm.chat@^0 + with: + api: "openai" + endpoint: "http://inference.internal:8000/v1/chat/completions" + model: "meta-llama/Llama-3.1-8B-Instruct" + prompt: $inputs.text +``` + +Gegen die Anthropic Messages API: + +```yaml +steps: + - id: classify + use: llm.chat@^0 + with: + api: "anthropic" + endpoint: "https://api.anthropic.com/v1/messages" + model: "claude-fable-5" + api_key: $inputs.anthropic_key + prompt: $inputs.text +``` + ## Build ```bash diff --git a/MODULE.md b/MODULE.md index 49460ad..cb8c167 100644 --- a/MODULE.md +++ b/MODULE.md @@ -1,23 +1,35 @@ # llm.chat -Generic Ollama-compatible LLM chat adapter. The building-block -module any flow uses when it needs a one-shot LLM completion — -classify, extract-fields, rewrite, decide — instead of rolling -its own HTTP client. +Generic LLM chat adapter. The building-block module any flow +uses when it needs a one-shot LLM completion — classify, +extract-fields, rewrite, decide — instead of rolling its own +HTTP client. Speaks three wire formats, selected by the +optional `api` input: Ollama (default), OpenAI Chat +Completions (OpenAI, vLLM, compatible servers), and the +Anthropic Messages API. ## Capability -- `llm.chat@0.1.0` +- `llm.chat@0.2.0` ## Inputs | Name | Type | Description | | ---------------- | ---- | --------------------------------------------------------------- | | `prompt` | text | The user-facing prompt. | -| `endpoint` | text | Ollama-shaped `/api/chat` URL (e.g. `http://localhost:11434/api/chat`). | -| `model` | text | Model identifier (e.g. `qwen2.5:14b`, `llama3.1:8b`). | -| `api_key` | text | Optional bearer token for cloud-hosted endpoints. | +| `endpoint` | text | Chat endpoint URL matching the selected `api` (see below). | +| `model` | text | Model identifier (e.g. `qwen2.5:14b`, `claude-fable-5`). | +| `api_key` | text | Optional API key. Never appears in outputs, logs, or events. | | `system_prompt` | text | Optional system message. Empty = use the model's default. | +| `api` | text | Optional wire format: `ollama` (default), `openai`, `anthropic`. | + +### Wire formats (`api`) + +| `api` | Endpoint shape | Auth header | Notes | +| ----------- | --------------------------------------------- | ---------------------------------- | ------------------------------------------------------------ | +| `ollama` | `http://localhost:11434/api/chat` | `Authorization: Bearer` (optional) | v0.1.0 behavior, unchanged. Model-digest probe via `/api/show`. | +| `openai` | `https://.../v1/chat/completions` | `Authorization: Bearer` | OpenAI Chat Completions format — also spoken by vLLM and most self-hosted inference servers. | +| `anthropic` | `https://api.anthropic.com/v1/messages` | `x-api-key` + `anthropic-version` | Messages API: `system` is a top-level field; `max_tokens` is mandatory and defaults to 4096. | ## Outputs @@ -26,7 +38,7 @@ its own HTTP client. | `response` | text | The assistant's plain-text reply. | | `model_endpoint` | text | The endpoint the response was generated against. | | `model_name` | text | The model identifier as supplied to the LLM API. | -| `model_digest` | text | SHA-256 digest of the served Ollama model. Empty for cloud APIs. | +| `model_digest` | text | SHA-256 digest of the served Ollama model. Empty for `openai`/`anthropic` — those APIs expose no digest; the field is left empty rather than fabricated. | Together the three `model_*` outputs answer the audit question: "which exact model produced this response?" — that @@ -43,7 +55,7 @@ permissions: - "net: api.anthropic.com" ``` -Loopback (local Ollama) by default. Cloud endpoints require an +Loopback (local Ollama / vLLM) by default. Cloud endpoints require an operator-policy override in `~/.chain/config.yaml#security.max_permissions`. ## Why this module instead of inline HTTP @@ -59,23 +71,22 @@ Three reasons: the installed modules + its declared `net:` list. A home-grown HTTP module would either hide its endpoints or be a fresh review surface every time. -3. **Endpoint portability.** The endpoint + model are flow - inputs, not compile-time constants. The same flow runs - against `localhost:11434` in dev and a production - inference cluster in prod just by swapping the input. +3. **Endpoint portability.** The endpoint, model, and wire + format are flow inputs, not compile-time constants. The + same flow runs against `localhost:11434` in dev and a + production vLLM cluster in prod just by swapping inputs. -## Limits in v0.1.0 +## Limits in v0.2.0 - No streaming. The whole reply is buffered before the output step fires. - No tool-call / function-call surface. A flow needing tool use composes multiple `llm.chat` steps with prompt engineering, or uses MCP via the bridge. -- Cloud-provider adapters for OpenAI and Anthropic are - deferred until a flow actually needs them. Today the - Ollama wire-format is the only target. +- Anthropic `max_tokens` is fixed at 4096 (the API requires + the field; a configurable input lands when a flow needs it). -## Example flow +## Example flows ```yaml name: classify-incoming @@ -96,6 +107,33 @@ outputs: audit_model: $classify.model_digest ``` +Against a vLLM (OpenAI-compatible) server: + +```yaml +steps: + - id: classify + use: llm.chat@^0 + with: + api: "openai" + endpoint: "http://inference.internal:8000/v1/chat/completions" + model: "meta-llama/Llama-3.1-8B-Instruct" + prompt: $inputs.text +``` + +Against the Anthropic Messages API: + +```yaml +steps: + - id: classify + use: llm.chat@^0 + with: + api: "anthropic" + endpoint: "https://api.anthropic.com/v1/messages" + model: "claude-fable-5" + api_key: $inputs.anthropic_key + prompt: $inputs.text +``` + ## Build ```bash diff --git a/README.md b/README.md index 275de8d..5e55757 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # llm-chat F∆I module providing the `llm.chat` capability — a generic -Ollama-compatible LLM chat adapter. +LLM chat adapter speaking Ollama (default), OpenAI Chat +Completions (OpenAI, vLLM, compatible servers), and the +Anthropic Messages API, selected via the optional `api` input. ## Capability | Field | Value | |-------|-------| -| Capability | `llm.chat@0.1.0` | -| Inputs | `prompt: text`, `endpoint: text`, `model: text`, `api_key: text` (opt), `system_prompt: text` (opt) | +| Capability | `llm.chat@0.2.0` | +| Inputs | `prompt: text`, `endpoint: text`, `model: text`, `api_key: text` (opt), `system_prompt: text` (opt), `api: text` (opt: `ollama`\|`openai`\|`anthropic`) | | Outputs | `response: text`, `model_endpoint: text`, `model_name: text`, `model_digest: text` | | Permissions | `net: localhost`, `net: 127.0.0.1`, `net: api.openai.com`, `net: api.anthropic.com` | | Status (in store index) | `alpha` | @@ -41,7 +43,7 @@ cargo build --release --target wasm32-wasip2 ## Test ```bash -cargo test # 11 tests, all host-side +cargo test # wire-format unit tests, all host-side cargo build --release --target wasm32-wasip2 ``` diff --git a/module.wasm b/module.wasm index cca97496ba1b52204c995ad28e04b0ab0d31ba27..353c54731c79ce1d939e1f6a9a9b3911647e9f50 100644 GIT binary patch delta 74315 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 QMEIawKEdM58YBDv0Ad1AZ~y=R delta 64919 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 Result { .get("system_prompt") .and_then(payload_text) .unwrap_or_default(); + let api_raw = inputs.get("api").and_then(payload_text).unwrap_or_default(); + let api = + crate::llm::Api::parse(&api_raw).map_err(|e| ModuleError::invalid_input(e.to_string()))?; let client = make_client(); let params = crate::llm::ChatParams { + api, endpoint: &endpoint, model: &model, api_key: &api_key, @@ -35,7 +44,7 @@ pub fn invoke(_ctx: Context, inputs: Inputs) -> Result { prompt: &prompt, }; let result = crate::llm::chat_with_identity(&client, ¶ms) - .map_err(|e| llm_error_to_module_error(e, &endpoint, &model))?; + .map_err(|e| llm_error_to_module_error(e, api, &endpoint, &model))?; Ok(Outputs::new() .with_text("response", result.response) @@ -53,29 +62,50 @@ fn payload_text(p: &Payload) -> Option { /// Turn a transport/protocol error into a message that names the /// likely cause and the fix, instead of a raw `ConnectionRefused`. -/// The endpoint is Ollama-shaped by default, so a connect failure -/// almost always means Ollama isn't running or the model isn't -/// pulled. -fn llm_error_to_module_error(e: crate::llm::LlmError, endpoint: &str, model: &str) -> ModuleError { - use crate::llm::LlmError; - match e { - LlmError::Http(detail) => ModuleError::internal(format!( +/// The hints are api-specific: an Ollama connect failure almost +/// always means Ollama isn't running or the model isn't pulled, +/// while cloud/vLLM failures are usually endpoint or key issues. +/// The message must never contain the api_key. +fn llm_error_to_module_error( + e: crate::llm::LlmError, + api: crate::llm::Api, + endpoint: &str, + model: &str, +) -> ModuleError { + use crate::llm::{Api, LlmError}; + match (api, e) { + (Api::Ollama, LlmError::Http(detail)) => ModuleError::internal(format!( "LLM endpoint {endpoint} not reachable ({detail}). Is Ollama running? \ Start it with `ollama serve`, then pull the model with `ollama pull {model}`. \ If the LLM runs elsewhere, set the `endpoint` input to its /api URL." )), - LlmError::Status(404) => ModuleError::internal(format!( + (Api::Openai, LlmError::Http(detail)) => ModuleError::internal(format!( + "LLM endpoint {endpoint} not reachable ({detail}). Expected an \ + OpenAI-compatible server (OpenAI, vLLM, ...) at a /v1/chat/completions URL." + )), + (Api::Anthropic, LlmError::Http(detail)) => ModuleError::internal(format!( + "LLM endpoint {endpoint} not reachable ({detail}). Expected the \ + Anthropic Messages API at a /v1/messages URL." + )), + (Api::Ollama, LlmError::Status(404)) => ModuleError::internal(format!( "LLM endpoint {endpoint} returned 404 for model '{model}' — the model is \ likely not pulled. Run `ollama pull {model}` (or check the model name)." )), - LlmError::Status(code) => ModuleError::internal(format!( + (_, LlmError::Status(401)) | (_, LlmError::Status(403)) => ModuleError::internal(format!( + "LLM endpoint {endpoint} rejected the request as unauthorized — check the \ + `api_key` input." + )), + (_, LlmError::Status(code)) => ModuleError::internal(format!( "LLM endpoint {endpoint} returned HTTP {code} for model '{model}'." )), - LlmError::Decode(detail) => ModuleError::internal(format!( - "LLM response from {endpoint} was not valid Ollama JSON: {detail}" + (_, LlmError::Decode(detail)) => ModuleError::internal(format!( + "LLM response from {endpoint} did not match the expected schema: {detail}" )), - LlmError::MissingInput(name) => ModuleError::invalid_input(format!( - "missing required input '{name}'" + (_, LlmError::MissingInput(name)) => { + ModuleError::invalid_input(format!("missing required input '{name}'")) + } + (_, LlmError::UnsupportedApi(raw)) => ModuleError::invalid_input(format!( + "unsupported api '{raw}' (expected: ollama, openai, anthropic)" )), } } @@ -100,7 +130,7 @@ impl crate::llm::LlmClient for HostStubClient { &self, _url: &str, _body: &str, - _api_key: &str, + _headers: &[(&'static str, String)], ) -> Result { Err(crate::llm::LlmError::Http( "LLM HTTP path is unavailable on the host build; only wasm32 supports outbound HTTP" @@ -118,14 +148,14 @@ impl crate::llm::LlmClient for WakiClient { &self, url: &str, body: &str, - api_key: &str, + headers: &[(&'static str, String)], ) -> Result { let mut request = waki::Client::new() .post(url) .header("Content-Type", "application/json") .body(body.to_string()); - if !api_key.is_empty() { - request = request.header("Authorization", &format!("Bearer {api_key}")); + for (name, value) in headers { + request = request.header(*name, value); } let response = request .send() diff --git a/src/llm.rs b/src/llm.rs index e09707f..90565f5 100644 --- a/src/llm.rs +++ b/src/llm.rs @@ -1,35 +1,84 @@ -//! Ollama-shaped chat client. +//! Multi-API chat client: Ollama (default), OpenAI, Anthropic. //! -//! v0.1.0 targets the Ollama `/api/chat` endpoint. OpenAI- and -//! Anthropic-compatible adapters are deliberately deferred — each -//! has its own request/response shape that warrants its own crate -//! (or at least its own module here) once a flow needs it. +//! The wire format is selected via the optional `api` input: +//! +//! - `ollama` (default) — Ollama `/api/chat`; unchanged v0.1.0 +//! behavior including the best-effort model-digest probe. +//! - `openai` — OpenAI Chat Completions wire format +//! (`/v1/chat/completions`), the de-facto standard implemented +//! by vLLM and most self-hosted inference servers. Bearer auth. +//! - `anthropic` — Anthropic Messages API (`/v1/messages`), +//! `x-api-key` + `anthropic-version` headers, top-level +//! `system` field, mandatory `max_tokens`. +//! +//! No streaming, no tool calls — one prompt in, one completion out. //! //! All HTTP I/O lives behind a `LlmClient` trait so unit tests can -//! exercise prompt building, response parsing, and the digest +//! exercise request building, response parsing, and the digest //! probe on the host without making real network calls. use serde::Serialize; +/// Anthropic's Messages API requires `max_tokens`. This default is +/// large enough for document-processing completions while staying +/// well below every current model's output cap. +const ANTHROPIC_DEFAULT_MAX_TOKENS: u32 = 4096; + +/// Pinned Messages API version header. Anthropic keeps old +/// versions working; bump deliberately, never implicitly. +const ANTHROPIC_VERSION: &str = "2023-06-01"; + #[allow(dead_code)] #[derive(Debug, thiserror::Error)] pub enum LlmError { #[error("missing required input '{0}'")] MissingInput(&'static str), + #[error("unsupported api '{0}' (expected: ollama, openai, anthropic)")] + UnsupportedApi(String), #[error("http error: {0}")] Http(String), #[error("non-success status: {0}")] Status(u16), - #[error("response body could not be parsed as Ollama schema: {0}")] + #[error("response body could not be parsed as the expected schema: {0}")] Decode(String), } +/// Which wire format to speak. Selected by the `api` input. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Api { + Ollama, + Openai, + Anthropic, +} + +impl Api { + /// Parse the `api` input. Empty string means the input was not + /// provided and falls back to Ollama (v0.1.0 behavior). + pub fn parse(raw: &str) -> Result { + match raw.trim().to_ascii_lowercase().as_str() { + "" | "ollama" => Ok(Api::Ollama), + "openai" => Ok(Api::Openai), + "anthropic" => Ok(Api::Anthropic), + other => Err(LlmError::UnsupportedApi(other.to_string())), + } + } +} + pub trait LlmClient { - fn post_json(&self, url: &str, body: &str, api_key: &str) -> Result; + /// POST `body` as JSON to `url` with the given extra headers. + /// `Content-Type: application/json` is implied. Header VALUES + /// may carry credentials — implementations must never log them. + fn post_json( + &self, + url: &str, + body: &str, + headers: &[(&'static str, String)], + ) -> Result; } #[derive(Debug, Clone)] pub struct ChatParams<'a> { + pub api: Api, pub endpoint: &'a str, pub model: &'a str, pub api_key: &'a str, @@ -37,8 +86,29 @@ pub struct ChatParams<'a> { pub prompt: &'a str, } +/// Auth/protocol headers for the selected API. The api_key is only +/// ever placed into a header value here; it must not appear in any +/// error, output, or log line. +pub fn build_headers(p: &ChatParams) -> Vec<(&'static str, String)> { + let mut headers = Vec::with_capacity(2); + match p.api { + Api::Ollama | Api::Openai => { + if !p.api_key.is_empty() { + headers.push(("Authorization", format!("Bearer {}", p.api_key))); + } + } + Api::Anthropic => { + if !p.api_key.is_empty() { + headers.push(("x-api-key", p.api_key.to_string())); + } + headers.push(("anthropic-version", ANTHROPIC_VERSION.to_string())); + } + } + headers +} + #[derive(Serialize)] -struct OllamaMessage<'a> { +struct ChatMessage<'a> { role: &'a str, content: &'a str, } @@ -46,33 +116,86 @@ struct OllamaMessage<'a> { #[derive(Serialize)] struct OllamaRequest<'a> { model: &'a str, - messages: Vec>, + messages: Vec>, stream: bool, } -/// Build the request body for an Ollama `/api/chat` invocation. -/// The system prompt is omitted from the messages list when empty -/// so a deployment can use the model's built-in system prompt. -pub fn build_ollama_body(p: &ChatParams) -> String { +#[derive(Serialize)] +struct OpenAiRequest<'a> { + model: &'a str, + messages: Vec>, + stream: bool, +} + +#[derive(Serialize)] +struct AnthropicRequest<'a> { + model: &'a str, + max_tokens: u32, + #[serde(skip_serializing_if = "Option::is_none")] + system: Option<&'a str>, + messages: Vec>, +} + +/// System-then-user message list shared by the Ollama and OpenAI +/// wire formats. The system message is omitted when empty so a +/// deployment can use the model's built-in system prompt. +fn build_messages<'a>(p: &ChatParams<'a>) -> Vec> { let mut messages = Vec::with_capacity(2); if !p.system_prompt.is_empty() { - messages.push(OllamaMessage { + messages.push(ChatMessage { role: "system", content: p.system_prompt, }); } - messages.push(OllamaMessage { + messages.push(ChatMessage { role: "user", content: p.prompt, }); + messages +} + +/// Build the request body for an Ollama `/api/chat` invocation. +pub fn build_ollama_body(p: &ChatParams) -> String { let req = OllamaRequest { model: p.model, - messages, + messages: build_messages(p), stream: false, }; serde_json::to_string(&req).unwrap_or_else(|_| String::from("{}")) } +/// Build the request body for an OpenAI-compatible +/// `/v1/chat/completions` invocation (OpenAI, vLLM, most +/// self-hosted inference servers). +pub fn build_openai_body(p: &ChatParams) -> String { + let req = OpenAiRequest { + model: p.model, + messages: build_messages(p), + stream: false, + }; + serde_json::to_string(&req).unwrap_or_else(|_| String::from("{}")) +} + +/// Build the request body for an Anthropic `/v1/messages` +/// invocation. `system` is a top-level field (not a message); +/// `max_tokens` is mandatory in the Messages API. +pub fn build_anthropic_body(p: &ChatParams) -> String { + let req = AnthropicRequest { + model: p.model, + max_tokens: ANTHROPIC_DEFAULT_MAX_TOKENS, + system: if p.system_prompt.is_empty() { + None + } else { + Some(p.system_prompt) + }, + messages: vec![ChatMessage { + role: "user", + content: p.prompt, + }], + }; + serde_json::to_string(&req).unwrap_or_else(|_| String::from("{}")) +} + /// Extract the assistant's message text from an Ollama /// /api/chat response. pub fn extract_ollama_content(body: &str) -> Result { @@ -85,16 +208,44 @@ pub fn extract_ollama_content(body: &str) -> Result { .ok_or_else(|| LlmError::Decode("missing message.content".into())) } +/// Extract the assistant text from an OpenAI Chat Completions +/// response: `choices[0].message.content`. +pub fn extract_openai_content(body: &str) -> Result { + let v: serde_json::Value = + serde_json::from_str(body).map_err(|e| LlmError::Decode(e.to_string()))?; + v.get("choices") + .and_then(|c| c.get(0)) + .and_then(|c| c.get("message")) + .and_then(|m| m.get("content")) + .and_then(|c| c.as_str()) + .map(|s| s.to_string()) + .ok_or_else(|| LlmError::Decode("missing choices[0].message.content".into())) +} + +/// Extract the assistant text from an Anthropic Messages +/// response: `content[0].text`. +pub fn extract_anthropic_content(body: &str) -> Result { + let v: serde_json::Value = + serde_json::from_str(body).map_err(|e| LlmError::Decode(e.to_string()))?; + v.get("content") + .and_then(|c| c.get(0)) + .and_then(|c| c.get("text")) + .and_then(|t| t.as_str()) + .map(|s| s.to_string()) + .ok_or_else(|| LlmError::Decode("missing content[0].text".into())) +} + #[derive(Debug, Clone)] pub struct ChatWithIdentity { pub response: String, pub model_digest: Option, } -/// Run an Ollama chat call AND probe the model digest. Probe is -/// best-effort — non-Ollama endpoints (no `/api/chat` suffix) and -/// transient failures yield `model_digest = None` rather than -/// failing the whole call. +/// Run one chat call in the selected wire format AND, for Ollama, +/// probe the model digest. The probe is best-effort — non-Ollama +/// APIs expose no digest endpoint, so `model_digest = None` there +/// (documented empty, never invented); transient probe failures on +/// Ollama also yield `None` rather than failing the whole call. pub fn chat_with_identity( client: &C, p: &ChatParams, @@ -108,10 +259,22 @@ pub fn chat_with_identity( if p.prompt.is_empty() { return Err(LlmError::MissingInput("prompt")); } - let body = build_ollama_body(p); - let response_body = client.post_json(p.endpoint, &body, p.api_key)?; - let response = extract_ollama_content(&response_body)?; - let model_digest = probe_model_digest(client, p); + let body = match p.api { + Api::Ollama => build_ollama_body(p), + Api::Openai => build_openai_body(p), + Api::Anthropic => build_anthropic_body(p), + }; + let headers = build_headers(p); + let response_body = client.post_json(p.endpoint, &body, &headers)?; + let response = match p.api { + Api::Ollama => extract_ollama_content(&response_body)?, + Api::Openai => extract_openai_content(&response_body)?, + Api::Anthropic => extract_anthropic_content(&response_body)?, + }; + let model_digest = match p.api { + Api::Ollama => probe_model_digest(client, p), + Api::Openai | Api::Anthropic => None, + }; Ok(ChatWithIdentity { response, model_digest, @@ -121,7 +284,8 @@ pub fn chat_with_identity( fn probe_model_digest(client: &C, p: &ChatParams) -> Option { let show_url = derive_show_url(p.endpoint)?; let body = serde_json::to_string(&serde_json::json!({ "name": p.model })).ok()?; - let response = client.post_json(&show_url, &body, p.api_key).ok()?; + let headers = build_headers(p); + let response = client.post_json(&show_url, &body, &headers).ok()?; extract_show_digest(&response) } @@ -160,20 +324,39 @@ mod tests { use super::*; use std::cell::RefCell; + /// (url, body, headers) of one recorded call. + type RecordedCall = (String, String, Vec<(String, String)>); + struct MockClient { responses: RefCell>>, + /// Every call, in call order. + calls: RefCell>, } impl MockClient { fn new(responses: Vec>) -> Self { Self { responses: RefCell::new(responses), + calls: RefCell::new(Vec::new()), } } } impl LlmClient for MockClient { - fn post_json(&self, _url: &str, _body: &str, _api_key: &str) -> Result { + fn post_json( + &self, + url: &str, + body: &str, + headers: &[(&'static str, String)], + ) -> Result { + self.calls.borrow_mut().push(( + url.to_string(), + body.to_string(), + headers + .iter() + .map(|(k, v)| (k.to_string(), v.clone())) + .collect(), + )); self.responses .borrow_mut() .pop() @@ -181,14 +364,42 @@ mod tests { } } - #[test] - fn ollama_body_includes_system_when_provided() { - let p = ChatParams { + fn params<'a>(api: Api) -> ChatParams<'a> { + ChatParams { + api, endpoint: "http://x/api/chat", model: "qwen", api_key: "", - system_prompt: "be helpful", + system_prompt: "", prompt: "hello", + } + } + + // ---------------- api input parsing ---------------- + + #[test] + fn api_parse_defaults_to_ollama() { + assert_eq!(Api::parse("").unwrap(), Api::Ollama); + assert_eq!(Api::parse("ollama").unwrap(), Api::Ollama); + assert_eq!(Api::parse(" OpenAI ").unwrap(), Api::Openai); + assert_eq!(Api::parse("anthropic").unwrap(), Api::Anthropic); + } + + #[test] + fn api_parse_rejects_unknown() { + assert!(matches!( + Api::parse("gemini"), + Err(LlmError::UnsupportedApi(_)) + )); + } + + // ---------------- Ollama wire format (regression) ---------------- + + #[test] + fn ollama_body_includes_system_when_provided() { + let p = ChatParams { + system_prompt: "be helpful", + ..params(Api::Ollama) }; let body = build_ollama_body(&p); let v: serde_json::Value = serde_json::from_str(&body).unwrap(); @@ -198,18 +409,12 @@ mod tests { assert_eq!(messages[0]["content"], "be helpful"); assert_eq!(messages[1]["role"], "user"); assert_eq!(messages[1]["content"], "hello"); + assert_eq!(v["stream"], false); } #[test] fn ollama_body_omits_system_when_empty() { - let p = ChatParams { - endpoint: "http://x/api/chat", - model: "qwen", - api_key: "", - system_prompt: "", - prompt: "hello", - }; - let body = build_ollama_body(&p); + let body = build_ollama_body(¶ms(Api::Ollama)); let v: serde_json::Value = serde_json::from_str(&body).unwrap(); let messages = v["messages"].as_array().unwrap(); assert_eq!(messages.len(), 1); @@ -231,6 +436,208 @@ mod tests { )); } + #[test] + fn ollama_bearer_header_only_with_key() { + assert!(build_headers(¶ms(Api::Ollama)).is_empty()); + let p = ChatParams { + api_key: "sk-test", + ..params(Api::Ollama) + }; + assert_eq!( + build_headers(&p), + vec![("Authorization", "Bearer sk-test".to_string())] + ); + } + + // ---------------- OpenAI wire format ---------------- + + #[test] + fn openai_body_matches_chat_completions_format() { + let p = ChatParams { + api: Api::Openai, + endpoint: "http://vllm:8000/v1/chat/completions", + model: "meta-llama/Llama-3.1-8B-Instruct", + api_key: "sk-x", + system_prompt: "be terse", + prompt: "hello", + }; + let body = build_openai_body(&p); + let v: serde_json::Value = serde_json::from_str(&body).unwrap(); + assert_eq!(v["model"], "meta-llama/Llama-3.1-8B-Instruct"); + assert_eq!(v["stream"], false); + let messages = v["messages"].as_array().unwrap(); + assert_eq!(messages.len(), 2); + assert_eq!(messages[0]["role"], "system"); + assert_eq!(messages[0]["content"], "be terse"); + assert_eq!(messages[1]["role"], "user"); + assert_eq!(messages[1]["content"], "hello"); + // No Anthropic-only fields leak into the OpenAI body. + assert!(v.get("max_tokens").is_none()); + assert!(v.get("system").is_none()); + } + + #[test] + fn openai_response_parses_fixture() { + // Shape as returned by OpenAI / vLLM /v1/chat/completions. + let body = r#"{ + "id": "chatcmpl-123", + "object": "chat.completion", + "created": 1719000000, + "model": "meta-llama/Llama-3.1-8B-Instruct", + "choices": [{ + "index": 0, + "message": {"role": "assistant", "content": "Hi from vLLM"}, + "finish_reason": "stop" + }], + "usage": {"prompt_tokens": 9, "completion_tokens": 4, "total_tokens": 13} + }"#; + assert_eq!(extract_openai_content(body).unwrap(), "Hi from vLLM"); + } + + #[test] + fn openai_response_errors_on_missing_choices() { + assert!(matches!( + extract_openai_content(r#"{"object":"chat.completion","choices":[]}"#), + Err(LlmError::Decode(_)) + )); + assert!(matches!( + extract_openai_content(r#"{"error":{"message":"invalid key"}}"#), + Err(LlmError::Decode(_)) + )); + } + + #[test] + fn openai_uses_bearer_auth() { + let p = ChatParams { + api_key: "sk-test", + ..params(Api::Openai) + }; + assert_eq!( + build_headers(&p), + vec![("Authorization", "Bearer sk-test".to_string())] + ); + } + + #[test] + fn openai_chat_returns_content_and_no_digest() { + let canned = r#"{"choices":[{"message":{"role":"assistant","content":"ok"}}]}"#; + let client = MockClient::new(vec![Ok(canned.to_string())]); + let p = ChatParams { + api: Api::Openai, + endpoint: "http://vllm:8000/v1/chat/completions", + model: "m", + api_key: "sk", + system_prompt: "", + prompt: "hi", + }; + let result = chat_with_identity(&client, &p).unwrap(); + assert_eq!(result.response, "ok"); + // No digest API on OpenAI-compatible endpoints — documented + // empty, and exactly one HTTP call (no probe). + assert_eq!(result.model_digest, None); + assert_eq!(client.calls.borrow().len(), 1); + } + + // ---------------- Anthropic wire format ---------------- + + #[test] + fn anthropic_body_matches_messages_format() { + let p = ChatParams { + api: Api::Anthropic, + endpoint: "https://api.anthropic.com/v1/messages", + model: "claude-fable-5", + api_key: "sk-ant", + system_prompt: "be terse", + prompt: "hello", + }; + let body = build_anthropic_body(&p); + let v: serde_json::Value = serde_json::from_str(&body).unwrap(); + assert_eq!(v["model"], "claude-fable-5"); + // max_tokens is mandatory in the Messages API. + assert_eq!(v["max_tokens"], 4096); + // system is a top-level field, never a message. + assert_eq!(v["system"], "be terse"); + let messages = v["messages"].as_array().unwrap(); + assert_eq!(messages.len(), 1); + assert_eq!(messages[0]["role"], "user"); + assert_eq!(messages[0]["content"], "hello"); + // No OpenAI/Ollama-only fields leak in. + assert!(v.get("stream").is_none()); + } + + #[test] + fn anthropic_body_omits_system_when_empty() { + let body = build_anthropic_body(¶ms(Api::Anthropic)); + let v: serde_json::Value = serde_json::from_str(&body).unwrap(); + assert!(v.get("system").is_none()); + assert_eq!(v["max_tokens"], 4096); + } + + #[test] + fn anthropic_response_parses_fixture() { + // Shape as returned by the Anthropic Messages API. + let body = r#"{ + "id": "msg_01XFDUDYJgAACzvnptvVoYEL", + "type": "message", + "role": "assistant", + "model": "claude-fable-5", + "content": [{"type": "text", "text": "Hi from Claude"}], + "stop_reason": "end_turn", + "usage": {"input_tokens": 10, "output_tokens": 5} + }"#; + assert_eq!(extract_anthropic_content(body).unwrap(), "Hi from Claude"); + } + + #[test] + fn anthropic_response_errors_on_missing_content() { + assert!(matches!( + extract_anthropic_content(r#"{"type":"message","content":[]}"#), + Err(LlmError::Decode(_)) + )); + assert!(matches!( + extract_anthropic_content(r#"{"type":"error","error":{"message":"x"}}"#), + Err(LlmError::Decode(_)) + )); + } + + #[test] + fn anthropic_uses_x_api_key_and_version_headers() { + let p = ChatParams { + api_key: "sk-ant-test", + ..params(Api::Anthropic) + }; + let headers = build_headers(&p); + assert_eq!( + headers, + vec![ + ("x-api-key", "sk-ant-test".to_string()), + ("anthropic-version", "2023-06-01".to_string()), + ] + ); + // Never Bearer auth on the Anthropic path. + assert!(headers.iter().all(|(k, _)| *k != "Authorization")); + } + + #[test] + fn anthropic_chat_returns_content_and_no_digest() { + let canned = r#"{"content":[{"type":"text","text":"ok"}]}"#; + let client = MockClient::new(vec![Ok(canned.to_string())]); + let p = ChatParams { + api: Api::Anthropic, + endpoint: "https://api.anthropic.com/v1/messages", + model: "claude-fable-5", + api_key: "sk-ant", + system_prompt: "", + prompt: "hi", + }; + let result = chat_with_identity(&client, &p).unwrap(); + assert_eq!(result.response, "ok"); + assert_eq!(result.model_digest, None); + assert_eq!(client.calls.borrow().len(), 1); + } + + // ---------------- digest probe (Ollama only) ---------------- + #[test] fn derive_show_url_swaps_chat_suffix() { assert_eq!( @@ -263,10 +670,7 @@ mod tests { let client = MockClient::new(vec![Ok(canned_show), Ok(canned_chat)]); let p = ChatParams { endpoint: "http://localhost:11434/api/chat", - model: "qwen", - api_key: "", - system_prompt: "", - prompt: "hi", + ..params(Api::Ollama) }; let result = chat_with_identity(&client, &p).unwrap(); assert_eq!(result.response, "the response"); @@ -279,10 +683,7 @@ mod tests { let client = MockClient::new(vec![Err(LlmError::Status(500)), Ok(canned_chat)]); let p = ChatParams { endpoint: "http://localhost:11434/api/chat", - model: "qwen", - api_key: "", - system_prompt: "", - prompt: "hi", + ..params(Api::Ollama) }; let result = chat_with_identity(&client, &p).unwrap(); assert_eq!(result.response, "ok"); @@ -290,30 +691,26 @@ mod tests { } #[test] - fn chat_with_identity_skips_probe_for_non_ollama() { + fn chat_with_identity_skips_probe_for_non_ollama_url() { let canned_chat = r#"{"message":{"content":"ok"}}"#.to_string(); let client = MockClient::new(vec![Ok(canned_chat)]); let p = ChatParams { - endpoint: "https://api.openai.com/v1/chat/completions", - model: "gpt", - api_key: "k", - system_prompt: "", - prompt: "hi", + endpoint: "https://example.com/proxy/chat", + ..params(Api::Ollama) }; let result = chat_with_identity(&client, &p).unwrap(); assert_eq!(result.response, "ok"); assert_eq!(result.model_digest, None); } + // ---------------- input guards ---------------- + #[test] fn empty_prompt_is_rejected() { let client = MockClient::new(vec![]); let p = ChatParams { - endpoint: "http://x/api/chat", - model: "x", - api_key: "", - system_prompt: "", prompt: "", + ..params(Api::Ollama) }; assert!(matches!( chat_with_identity(&client, &p),