Skip to content
Merged
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
6 changes: 4 additions & 2 deletions bookstack_agent/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
42 changes: 32 additions & 10 deletions src/aieng_bot/bookstack/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

"""

Expand All @@ -66,20 +71,37 @@ 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()

Expand Down