diff --git a/CLAUDE.md b/CLAUDE.md index d432fbb..92d486c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,6 +41,7 @@ lmproto/openai_compatible/ ← Base: model, config, client lmproto/openai/ ← OpenAI platform: Responses + chat (extends base) lmproto/anthropic/ ← Anthropic Messages: model, config, client lmproto/deepseek/openai_compat/ ← Extends base via inheritance + extra fields +lmproto/deepseek/responses/ ← DeepSeek Responses client (extends OpenAI Responses client; SSE stops at terminal events, no `[DONE]`) ``` - **`openai_compatible/model.py`** — tagged chat messages (`SystemChatMessage`, `UserChatMessage`, …), tools, request/response, streaming chunks. @@ -50,15 +51,15 @@ lmproto/deepseek/openai_compat/ ← Extends base via inheritance + extra fields - **`openai/client.py`** — platform `OpenAIClient` (`kind="responses"`): chat completions + `responses` / `get_response` / `delete_response`. - **`anthropic/model.py`** — Anthropic Messages request/response + SSE event models. - **`anthropic/client.py`** — `AnthropicClient` (`kind="messages"`): `POST /messages` + `GET /models`. -- **DeepSeek** — `DeepseekOpenAIClient` (`kind="chat_completions"`); models add `reasoning_content`, `prefix`, `ThinkingOptions`. Config default model ids: `deepseek-v4-flash`, `deepseek-v4-pro`. +- **DeepSeek** — `DeepseekOpenAIClient` (`kind="chat_completions"`); models add `reasoning_content`, `prefix`, `ThinkingOptions`. Config default model ids: `deepseek-v4-flash`, `deepseek-v4-pro`. `DeepseekResponsesClient` (`kind="responses"`) extends the OpenAI Responses client for `convention = "responses"` (SSE stops at terminal events — DeepSeek sends no `[DONE]`). ### Config (`config/`) -TOML load/store (`ConfigStore`): `[providers]` tagged union presets, `[database]` section. Default path via platformdirs. Providers may set `timeout` as a float or `{ connect, read }` (`HttpTimeoutConfig`); omitted → product defaults (10s connect / 600s read). +TOML load/store (`ConfigStore`): `[providers]` tagged union presets, `[database]` section. Default path via platformdirs. Providers may set `timeout` as a float or `{ connect, read }` (`HttpTimeoutConfig`); omitted → product defaults (10s connect / 600s read). `DeepseekProvider.convention` (Literal: `""`/`"openai"`/`"anthropic"`/`"responses"`) selects the DeepSeek API surface — `"responses"` → OpenAI Responses; a per-model `ModelConfig.convention` overrides it (empty = inherit). ### Runtime (`runtime/`) -`create_client(provider)` maps config `Provider` → protocol client by effective preset (model-level `preset`/`url` overrides apply). `normalize_http_timeout` feeds OpenAI-compatible configs and Anthropic. Mapping: `openai` → `OpenAIClient` (agent uses Responses), `openai-compatible` / `deepseek` → chat-completions clients, `anthropic` → `AnthropicClient`. +`create_client(provider)` maps config `Provider` → protocol client by effective preset (model-level `preset`/`url` overrides apply). `normalize_http_timeout` feeds OpenAI-compatible configs and Anthropic. Mapping: `openai` → `OpenAIClient` (agent uses Responses), `openai-compatible` / `deepseek` → chat-completions clients (deepseek with effective `convention = "responses"` → `DeepseekResponsesClient`), `anthropic` → `AnthropicClient`. ### Memory (`memory/`) diff --git a/README.md b/README.md index e5da484..18a5a2b 100644 --- a/README.md +++ b/README.md @@ -163,10 +163,20 @@ Supported provider presets today: |--------|------------------------|--------| | `openai` (default if `preset` omitted) | OpenAI **Responses** (`POST /responses`) | Default models `gpt-5.4` / `gpt-5.4-mini` / `gpt-5.4-nano` when `models` is omitted; optional `provider_tools` (default `web_search`) | | `openai-compatible` | Chat Completions | Generic hosts (vLLM, LiteLLM, proxies); requires `url` | -| `deepseek` | Chat Completions (DeepSeek) | Default models `deepseek-v4-flash` / `deepseek-v4-pro` when `models` is omitted | +| `deepseek` | Chat Completions (DeepSeek) | Default models `deepseek-v4-flash` / `deepseek-v4-pro` when `models` is omitted; set `convention = "responses"` (provider or per-model) to use the OpenAI **Responses** API (`POST /responses`, currently only `deepseek-v4-flash`) | | `anthropic` | Anthropic **Messages** (`POST /messages`) | Native tools/streaming; set `access_key_or_token` (API key) | -Model-level `preset` / `url` overrides on a catalog entry can route a single provider name to a different API (e.g. gateway + Anthropic model). +Model-level `preset` / `url` overrides on a catalog entry can route a single provider name to a different API (e.g. gateway + Anthropic model). DeepSeek models may also carry a per-model `convention` (empty = inherit the provider-level one), e.g. serve `deepseek-v4-flash` over Responses while `deepseek-v4-pro` stays Chat Completions: + +```toml +[providers.deepseek] +preset = "deepseek" +access_key_or_token = "sk-..." + +[providers.deepseek.models] +"deepseek-v4-flash" = { text = true, convention = "responses" } +"deepseek-v4-pro" = { text = true } +``` If `[database]` is omitted (or SQLite `url` is unset/empty), chat uses a durable file under the user data dir (e.g. `~/.local/share/plyngent/chat.db` on Linux). Set `url = ":memory:"` for a true in-memory SQLite (CLI warns; no file; useful for tests). diff --git a/doc/plyngent.example.toml b/doc/plyngent.example.toml index 513eb40..22da683 100644 --- a/doc/plyngent.example.toml +++ b/doc/plyngent.example.toml @@ -103,14 +103,21 @@ access_key_or_token = "sk-replace-me" # "claude-sonnet-4-20250514" = { text = true } # "claude-opus-4-20250514" = { text = true } -# --- DeepSeek (OpenAI-compatible convention) --- +# --- DeepSeek (OpenAI-compatible convention; optional Responses API) --- # Default models if [providers.*.models] is omitted: deepseek-v4-flash, deepseek-v4-pro. # [providers.deepseek] # preset = "deepseek" # access_key_or_token = "sk-replace-me" # # url defaults to https://api.deepseek.com when empty -# extras = { convention = "openai" } +# # Convention selects the API surface: +# # "" / "openai" → OpenAI-compatible chat completions (default) +# # "responses" → OpenAI Responses API (POST /responses; today only +# # deepseek-v4-flash supports it) +# # "anthropic" → legacy value, ignored (still chat completions) +# convention = "responses" +# # Legacy location (parsed but ignored; prefer the typed field above): +# # extras = { convention = "openai" } # # [providers.deepseek.models] -# "deepseek-v4-flash" = { text = true } +# "deepseek-v4-flash" = { text = true, convention = "responses" } # "deepseek-v4-pro" = { text = true }