Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
model: gpt-5.4 # LLM model (any LiteLLM-supported provider)
# Responses-only models (served exclusively on the provider's /v1/responses
# endpoint, e.g. some managed gateways): prefix with `openai-responses/`.
# OpenKB then talks the Responses API for compile/query/chat/lint/skills.
# Long-PDF PageIndex indexing still needs a Chat Completions model.
# model: openai-responses/<model-id>
language: en # Wiki output language
pageindex_threshold: 20 # PDF pages threshold for PageIndex

Expand Down
21 changes: 21 additions & 0 deletions examples/configuration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,27 @@ litellm:
timeout: 1200 # raise further (e.g. 3600) for large local models
```

#### Responses-only models (Responses API transport)

Some gateways serve certain models exclusively on the `/v1/responses`
endpoint and answer `/v1/chat/completions` with `503 Endpoint is
unavailable`. Prefix the model with `openai-responses/` and OpenKB talks
the Responses API for every agent call (compile, query, chat, lint,
skills, decks) — including tool calls and `run_streamed` sessions:

```yaml
model: openai-responses/<model-id>
language: en
```

The prefix is OpenKB-only (LiteLLM never sees it). Credentials work as
usual: `LLM_API_KEY` + `OPENAI_API_BASE` from `<kb>/.env`, plus any
`litellm.extra_headers` your gateway needs (e.g. a session-routing
header). Two limitations: long-PDF PageIndex indexing still requires a
Chat Completions model (`openkb add` fails fast with a clear error
otherwise — convert the PDF to Markdown first), and streaming is
supported through the Agents SDK path only.

#### GitHub Copilot / ChatGPT-subscription providers

These need extra headers and use OAuth (no API key):
Expand Down
11 changes: 9 additions & 2 deletions openkb/agent/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
)
from openkb.lint import list_existing_wiki_targets, strip_ghost_wikilinks
from openkb.locks import atomic_write_text
from openkb.responses import aresponses_completion, is_responses_model, responses_completion
from openkb.schema import INDEX_SEED, get_agents_md

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -425,7 +426,10 @@ def _llm_call(
spinner.start()
t0 = time.time()

response = litellm.completion(model=model, messages=messages, **kwargs)
if is_responses_model(model):
response = responses_completion(model=model, messages=messages, **kwargs)
else:
response = litellm.completion(model=model, messages=messages, **kwargs)
content = response.choices[0].message.content or ""
truncated = _warn_if_truncated(response, step_name, kwargs.get("max_tokens"))

Expand Down Expand Up @@ -466,7 +470,10 @@ async def _llm_call_async(

t0 = time.time()

response = await litellm.acompletion(model=model, messages=messages, **kwargs)
if is_responses_model(model):
response = await aresponses_completion(model=model, messages=messages, **kwargs)
else:
response = await litellm.acompletion(model=model, messages=messages, **kwargs)
content = response.choices[0].message.content or ""
truncated = _warn_if_truncated(response, step_name, kwargs.get("max_tokens"))

Expand Down
3 changes: 2 additions & 1 deletion openkb/agent/linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from openkb.agent.tools import list_wiki_files, read_wiki_file
from openkb.config import LlmCredentialBundle, resolve_model_settings
from openkb.responses import responses_agent_model
from openkb.schema import get_agents_md

MAX_TURNS = 50
Expand Down Expand Up @@ -97,7 +98,7 @@ def read_file(path: str) -> str:
name="wiki-linter",
instructions=instructions,
tools=[list_files, read_file],
model=f"litellm/{model}",
model=(responses_agent_model(model, bundle) or f"litellm/{model}"),
model_settings=ModelSettings(**model_settings),
)

Expand Down
16 changes: 14 additions & 2 deletions openkb/agent/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
write_kb_file,
)
from openkb.config import LlmCredentialBundle, resolve_model_settings
from openkb.responses import responses_agent_model
from openkb.schema import get_agents_md

MAX_TURNS = 50
Expand Down Expand Up @@ -118,7 +119,7 @@ def get_image(image_path: str) -> ToolOutputImage | ToolOutputText:
name="wiki-query",
instructions=instructions,
tools=[read_file, get_page_content, get_image],
model=f"litellm/{model}",
model=(responses_agent_model(model, bundle) or f"litellm/{model}"),
model_settings=ModelSettings(**model_settings),
)

Expand Down Expand Up @@ -506,9 +507,20 @@ def build_run_config_from_bundle(model: str, bundle: "LlmCredentialBundle | None
must NOT be added here -- doing so yields ``litellm/openai/...`` which
litellm rejects as an unknown provider.
"""
from agents import RunConfig

if bundle is None:
instance = responses_agent_model(model, None)
if instance is not None:
# Legacy CLI path for Responses models: credentials fall back
# to LLM_API_KEY / OPENAI_API_BASE env (loaded from the KB
# .env by _setup_llm_key before this is called).
return RunConfig(model=instance)
return None
from agents import RunConfig

instance = responses_agent_model(model, bundle)
if instance is not None:
return RunConfig(model=instance)
from agents.extensions.models.litellm_model import LitellmModel

litellm_model = LitellmModel(
Expand Down
7 changes: 7 additions & 0 deletions openkb/indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pageindex import IndexConfig, PageIndexClient

from openkb.config import resolve_concurrency, resolve_effective_config
from openkb.responses import is_responses_model
from openkb.tree_renderer import render_summary_md

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -191,6 +192,12 @@ def index_long_document(pdf_path: Path, kb_dir: Path, doc_name: str | None = Non
config = resolve_effective_config(kb_dir)[0]

model: str = config.get("model", "gpt-5.4")
if is_responses_model(model):
raise ValueError(
f"Long-PDF PageIndex indexing requires a Chat Completions model, "
f"but the KB is configured with Responses-only {model!r}. "
f"Convert the PDF to Markdown first, or switch the KB model."
)
pageindex_api_key = os.environ.get("PAGEINDEX_API_KEY", "")

index_config = _build_index_config(config)
Expand Down
Loading