LLM Provider Abstraction - Chris-Cullins/wiki_bot GitHub Wiki
LLM Provider Abstraction
Overview
The LLM Provider Abstraction isolates how the wiki bot talks to large language models across the Anthropic Agent SDK, Claude CLI, and Codex CLI backends. It centralizes provider selection, configuration, and command execution so the rest of the system can stream documentation tasks without knowing which LLM is behind the scenes. The abstraction also supplies a mock implementation for deterministic testing and feeds the wiki generator with consistent streaming responses regardless of provider.
Key Components
src/query-factory.ts: Builds the concreteQueryFunctionwrapper for the configured provider, including CLI adapters and shared command execution.src/config.ts: Parses environment variables into a typedConfig, normalizing provider choices, auth keys, and documentation depth flags.src/index.ts: Orchestrates runtime bootstrapping—loads configuration, resolves repo paths, and injects the provider-specific query function into theWikiGenerator.src/wiki-generator.ts: Consumes the provider-agnostic query function, streaming responses and transforming them into Markdown wiki pages.src/mock-agent-sdk.ts: Supplies a fake query function fortestMode, returning canned responses that mimic Agent SDK streaming.
How It Works
- Configuration Loading (
loadConfig): Environment variables are read on startup, converting string flags into booleans, enums, and defaults. Provider aliases (e.g.,claude) are normalized to the internalLlmProviderunion. - CLI Option Overrides (
parseCliArgsinsrc/index.ts): Runtime flags like--depthor--target-fileoverride loaded configuration before dependencies are initialized. - Query Function Creation (
createQueryFunction): Using the resolvedConfig, the factory picks one of three branches:- Agent SDK: Returns the SDK’s native
queryfunction with minimal wrapping. - Claude CLI / Codex CLI: Wraps a child process invocation in an async iterator, streaming stdout back as Agent-style messages.
- Test Mode: Short-circuits to
createMockQueryfor repeatable unit tests.
- Agent SDK: Returns the SDK’s native
- Command Execution (
runCommand): Shared helper spawns subprocesses, captures stdout/stderr, handles ENOENT errors, and surfaces non-zero exits with contextual messaging. Debug logging records prompt previews and output snippets. - Wiki Generation (
WikiGenerator): All documentation flows throughthis._query, which is the provider-agnostic function. The generator prepares prompts, logs them when debug is enabled, collects streamed responses, and post-processes Markdown (heading normalization, template rendering). - Mock Flow (
createMockQuery): For test mode, prompt inspection returns deterministic Markdown or JSON, letting integration tests exercise downstream logic without LLM calls.
Important Functions/Classes
createQueryFunction(config, repoPath, logger)(src/query-factory.ts): Switchboard that decides which provider to bind. Ensures test mode takes precedence over CLI or Agent SDK selection, so fixtures can run without external dependencies.createClaudeCliQuery(repoPath, logger)/createCodexCliQuery(logger)(src/query-factory.ts): Provider-specific adapters that format commands, feed prompts via stdin, and convert stdout into the streamingQueryshape expected by the Agent SDK consumer.runCommand(command, args, input, logger)(src/query-factory.ts): Robust subprocess executor that normalizes encoding, traps launch errors, and trims stderr/stdout for error reporting. Also detects “usage limit” phrases to aid rate-limit diagnostics.extractCodexResponse(output)(src/query-factory.ts): Parses newline-delimited JSON fromcodex exec --json -, collecting onlyagent_messagepayloads and collapsing them into a Markdown string.loadConfig()(src/config.ts): Converts env state into a validated configuration object, enforcing required API keys when the Agent SDK is selected and deriving booleans for toggles likePROMPT_LOG_ENABLED.WikiGenerator(src/wiki-generator.ts): Core documentation engine; methods likegenerateHomePage,generateArchitecturalOverview, andgenerateAreaDocumentationprepare prompts, call the query function, and normalize responses. Key helpers (collectResponseText,ensureHeading,ensureArchitectureOutline) ensure consistent Markdown regardless of provider quirks.createMockQuery()(src/mock-agent-sdk.ts): Produces an async iterator with canned responses that imitate Agent SDK message semantics, keeping the rest of the stack oblivious to the mock.
Developer Notes
- Provider Selection Order: Test mode overrides everything. When toggled via
TEST_MODE=true, the mock query is returned even ifLLM_PROVIDERpoints to CLI adapters. - Environment Validation:
loadConfigthrows if no Anthropic key is present while using the Agent SDK and test mode is off. Catch this early in automated deployments by settingLLM_PROVIDER=codex-cliorclaude-cliwhen keys are unavailable. - CLI Dependencies: The CLI branches assume binaries named
claudeandcodexare onPATH.runCommandconvertsENOENTinto a friendly install reminder—bubble this up to users instead of swallowing it. - Prompt Logging: When
promptLoggingEnabledis true,DebugLoggerpersists prompts/responses. The query factory and wiki generator both rely on the same logger, so togglingDEBUGor logging paths affects provider diagnostics and content generation equally. - Streaming Compatibility: The wiki generator expects Agent SDK-style streaming events. The CLI adapters fake this by returning a single
assistantmessage. If you add new providers, ensure they yield messages adhering toQuery’s async iterator contract. - Error Surface: CLI failures propagate as rejected promises. Upstream callers should be ready to handle thrown errors (e.g., wrapping
await wikiGenerator.generateHomePage(...)in try/catch when introducing new workflows).
Usage Examples
Selecting a Provider via Environment
# Use the Anthropic Agent SDK (requires API key)
export LLM_PROVIDER=agent-sdk
export ANTHROPIC_API_KEY=sk-ant-...
# Switch to Claude CLI without touching code
export LLM_PROVIDER=claude-cli
export CLAUDE_AUTH_TOKEN=...
# Enable deterministic test mode
export TEST_MODE=true
Injecting the Query Function at Startup (src/index.ts)
const config = loadConfig();
const repoPath = config.repoPath ? resolve(config.repoPath) : process.cwd();
const logger = new DebugLogger({ enabled: Boolean(config.debug) });
const queryFn = createQueryFunction(config, repoPath, logger);
const wikiGenerator = new WikiGenerator(queryFn, config, logger);
Consuming the Provider in Wiki Generation (src/wiki-generator.ts)
const prompt = await loadPrompt('generate-area-documentation', {
area,
fileContentText,
existingDoc: existingDoc ?? '',
depthInstruction: this.getDepthInstruction(),
});
const query = this.createQuery(prompt);
const response = await this.collectResponseText(query);
const finalized = this.ensureHeading(this.stripFenceWrappers(response), area);
These snippets demonstrate how configuration, provider abstraction, and documentation generation tie together, enabling the wiki bot to swap LLM backends without touching the core content pipeline.