From 27a86fe0ef652aa879af2a4715edc147973de8c2 Mon Sep 17 00:00:00 2001 From: Amrit Krishnan Date: Tue, 16 Jun 2026 08:30:47 -0400 Subject: [PATCH 1/2] fix(bookstack-agent): use Authorization: Bearer when routing via LLM gateway The Anthropic SDK sends x-api-key for auth, but the proxy gateway requires Authorization: Bearer. When LLM_BASE_URL is set, the agent now reads LLM_API_KEY (the gateway-issued bearer token) and injects it via default_headers so the gateway accepts the request. Previously the SDK sent x-api-key: directly to Anthropic (if LLM_BASE_URL was unset) or to the gateway (where it failed auth), both resulting in a 401 'invalid x-api-key'. Startup validation is also updated: when LLM_BASE_URL is set, LLM_API_KEY is required instead of ANTHROPIC_API_KEY. --- bookstack_agent/api/main.py | 6 +++-- src/aieng_bot/bookstack/agent.py | 40 ++++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/bookstack_agent/api/main.py b/bookstack_agent/api/main.py index df8bb9b..52d2d65 100644 --- a/bookstack_agent/api/main.py +++ b/bookstack_agent/api/main.py @@ -61,9 +61,11 @@ @asynccontextmanager async def lifespan(application: FastAPI) -> AsyncGenerator[None, None]: """Create the BookstackQAAgent and session store at startup.""" + llm_base_url = os.environ.get("LLM_BASE_URL") + llm_key_var = "LLM_API_KEY" if llm_base_url else "ANTHROPIC_API_KEY" missing = [ v - for v in ("ANTHROPIC_API_KEY", "BOOKSTACK_TOKEN_ID", "BOOKSTACK_TOKEN_SECRET") + for v in (llm_key_var, "BOOKSTACK_TOKEN_ID", "BOOKSTACK_TOKEN_SECRET") if not os.environ.get(v) ] if missing: @@ -77,7 +79,7 @@ async def lifespan(application: FastAPI) -> AsyncGenerator[None, None]: ), token_id=os.environ["BOOKSTACK_TOKEN_ID"], token_secret=os.environ["BOOKSTACK_TOKEN_SECRET"], - llm_base_url=os.environ.get("LLM_BASE_URL"), + llm_base_url=llm_base_url, ) # OrderedDict preserves insertion order for LRU-style eviction diff --git a/src/aieng_bot/bookstack/agent.py b/src/aieng_bot/bookstack/agent.py index f3b54a8..9bf127a 100644 --- a/src/aieng_bot/bookstack/agent.py +++ b/src/aieng_bot/bookstack/agent.py @@ -53,6 +53,11 @@ class BookstackQAAgent: ``https://proxy.vectorinstitute.ai``) instead of the Anthropic API directly. Defaults to ``LLM_BASE_URL`` env var, or the Anthropic API if unset. + llm_api_key : str, optional + Bearer token for the LLM gateway. Required when ``llm_base_url`` is + set; ignored otherwise. The gateway expects ``Authorization: Bearer`` + rather than the Anthropic ``x-api-key`` header. Defaults to + ``LLM_API_KEY`` env var. """ @@ -66,20 +71,35 @@ def __init__( api_key: str | None = None, model: str | None = None, llm_base_url: str | None = None, + llm_api_key: str | None = None, ) -> None: """Initialise the agent.""" - resolved_key = api_key or os.environ.get("ANTHROPIC_API_KEY") - if not resolved_key: - raise ValueError("ANTHROPIC_API_KEY environment variable not set") - resolved_llm_base_url = llm_base_url or os.environ.get("LLM_BASE_URL") - self._sync_client = anthropic.Anthropic( - api_key=resolved_key, base_url=resolved_llm_base_url - ) - self._async_client = anthropic.AsyncAnthropic( - api_key=resolved_key, base_url=resolved_llm_base_url - ) + if resolved_llm_base_url: + # Gateway mode: the gateway requires Authorization: Bearer, not x-api-key. + # LLM_API_KEY is the gateway-issued bearer token; ANTHROPIC_API_KEY is not used. + resolved_llm_api_key = llm_api_key or os.environ.get("LLM_API_KEY") + if not resolved_llm_api_key: + raise ValueError("LLM_API_KEY must be set when LLM_BASE_URL is configured") + self._sync_client = anthropic.Anthropic( + api_key=resolved_llm_api_key, + base_url=resolved_llm_base_url, + default_headers={"Authorization": f"Bearer {resolved_llm_api_key}"}, + ) + self._async_client = anthropic.AsyncAnthropic( + api_key=resolved_llm_api_key, + base_url=resolved_llm_base_url, + default_headers={"Authorization": f"Bearer {resolved_llm_api_key}"}, + ) + else: + # Direct Anthropic mode: standard x-api-key authentication. + resolved_key = api_key or os.environ.get("ANTHROPIC_API_KEY") + if not resolved_key: + raise ValueError("ANTHROPIC_API_KEY environment variable not set") + self._sync_client = anthropic.Anthropic(api_key=resolved_key) + self._async_client = anthropic.AsyncAnthropic(api_key=resolved_key) + self.bookstack = BookStackClient(base_url, token_id, token_secret) self.model = model or get_model_name() From 82165b3d9c848bb50be586c90d7277503a67f36a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 12:31:33 +0000 Subject: [PATCH 2/2] [pre-commit.ci] Add auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/aieng_bot/bookstack/agent.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/aieng_bot/bookstack/agent.py b/src/aieng_bot/bookstack/agent.py index 9bf127a..898b255 100644 --- a/src/aieng_bot/bookstack/agent.py +++ b/src/aieng_bot/bookstack/agent.py @@ -81,7 +81,9 @@ def __init__( # LLM_API_KEY is the gateway-issued bearer token; ANTHROPIC_API_KEY is not used. resolved_llm_api_key = llm_api_key or os.environ.get("LLM_API_KEY") if not resolved_llm_api_key: - raise ValueError("LLM_API_KEY must be set when LLM_BASE_URL is configured") + raise ValueError( + "LLM_API_KEY must be set when LLM_BASE_URL is configured" + ) self._sync_client = anthropic.Anthropic( api_key=resolved_llm_api_key, base_url=resolved_llm_base_url,