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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# Finder (MacOS) folder config
.DS_Store
qdrant_storage/
14 changes: 14 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@
"@ai-sdk/cohere": "^3.0.1",
"@ai-sdk/devtools": "^0.0.2",
"@ai-sdk/google": "^3.0.2",
"@ai-sdk/groq": "^3.0.4",
"@ai-sdk/mcp": "1.0.1",
"@ai-sdk/openai": "^3.0.2",
"@ai-sdk/openai-compatible": "^2.0.4",
"@ai-sdk/react": "^3.0.3",
"@elysiajs/cors": "^1.0.0",
"@qdrant/qdrant-js": "^1.16.2",
Expand Down
168 changes: 156 additions & 12 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Required:
- [Bun](https://bun.sh)
- [Qdrant](https://qdrant.tech) (local via Docker or remote - `docbot init` will set you up with a local instance via Docker)
- `rg` (ripgrep) for fast exact-match search
- `AI_GATEWAY_API_KEY` (Vercel AI Gateway)
- API key for your chosen provider (see [Provider Configuration](#provider-configuration))

## How It Works

Expand All @@ -72,7 +72,7 @@ Docbot is opinionated so we were able to build it fast, but it's not meant to st

- **Docs frameworks**: Today Docbot targets MDX-based doc sites and detects Mintlify project structure automatically (as long as you use `docs.json`). Mintlify was the first target because that's what we use at Helm; support will expand (custom MDX, Fumadocs, Nextra, Docusaurus, etc.). It's just a matter of tweaking the tools and prompts.
- **Vector store**: Currently Qdrant (required). It's easy to run locally and does the job well. This may evolve as CI/multi-user needs grow.
- **Models/provider**: Currently Vercel AI Gateway via `AI_GATEWAY_API_KEY`. Adding other providers is planned - you can use configure the models in the config file though.
- **Models/provider**: Defaults to Vercel AI Gateway (`AI_GATEWAY_API_KEY`). Alternatively, configure native providers (OpenAI, Anthropic, Google, Groq) or any OpenAI-compatible endpoint (LM Studio, Ollama, vLLM, self-hosted models). See [Provider Configuration](#provider-configuration).
- **Bun**: Required. Will not change.

## Commands
Expand Down Expand Up @@ -165,25 +165,169 @@ Example:
"codebase": ["./apps/web", "./packages/shared"]
},
"qdrant": {
"url": "http://127.0.0.1:6333",
"manifestPath": ".docbot/manifest.json",
"collections": {
"docs": "docbot_my-project_docs",
"code": "docbot_my-project_code"
}
"url": "http://127.0.0.1:6333"
},
"server": { "port": 3070 },
"models": {
"planning": "openai/gpt-5.2",
"prose": "anthropic/claude-sonnet-4.5",
"fast": "anthropic/claude-haiku-4.5",
"embedding": "openai/text-embedding-3-small",
"reranker": "cohere/rerank-v3.5"
"fast": "openai/gpt-5.2",
"nano": "google/gemini-3-flash",
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Inconsistent example: the nano model references google/gemini-3-flash but the providers array only includes openai and anthropic. Users following this example will get errors. Either add { "type": "google" } to providers, or change nano to use an openai/anthropic model.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At readme.md, line 175:

<comment>Inconsistent example: the `nano` model references `google/gemini-3-flash` but the `providers` array only includes `openai` and `anthropic`. Users following this example will get errors. Either add `{ "type": "google" }` to providers, or change nano to use an openai/anthropic model.</comment>

<file context>
@@ -165,25 +165,167 @@ Example:
-    "embedding": "openai/text-embedding-3-small",
-    "reranker": "cohere/rerank-v3.5"
+    "fast": "openai/gpt-5.2",
+    "nano": "google/gemini-3-flash",
+    "embedding": "openai/text-embedding-3-small"
   }
</file context>
Suggested change
"nano": "google/gemini-3-flash",
"nano": "openai/gpt-5.2",
Fix with Cubic

"embedding": "openai/text-embedding-3-small"
}
}
```

CLI flags take precedence over the config file.
CLI flags take precedence over the config file. See [Provider Configuration](#provider-configuration) for setting up different LLM providers.

## Provider Configuration

Docbot supports multiple LLM providers. By default, it uses Vercel AI Gateway. You can configure native providers or custom OpenAI-compatible endpoints.

### Default: Vercel AI Gateway

When no `providers` are configured, Docbot uses Vercel AI Gateway. Set your API key:

```bash
export AI_GATEWAY_API_KEY=your-gateway-key
```

### Native Providers

Configure native providers (OpenAI, Anthropic, Google, Groq) by adding them to the `providers` array. When any provider is configured, Docbot switches from gateway mode to native mode.

**Environment Variables:**

| Provider | Environment Variable |
|----------|---------------------|
| OpenAI | `OPENAI_API_KEY` |
| Anthropic | `ANTHROPIC_API_KEY` |
| Google | `GOOGLE_GENERATIVE_AI_API_KEY` |
| Groq | `GROQ_API_KEY` |

**Example: Using OpenAI and Anthropic directly**

```jsonc
{
"providers": [
{ "type": "openai" },
{ "type": "anthropic" }
],
"models": {
"planning": "openai/gpt-5.2",
"prose": "anthropic/claude-sonnet-4.5",
"fast": "openai/gpt-5.2",
"nano": "google/gemini-3-flash",
"embedding": "openai/text-embedding-3-small"
}
}
```

```bash
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-...
```

### OpenAI-Compatible Endpoints

Use any OpenAI-compatible API (Ollama, LM Studio, vLLM, self-hosted models) with the `openai-compatible` provider type.

**Example: Ollama (local)**

Note: Ensure you have an embedding model available in Ollama (e.g., `ollama pull nomic-embed-text`).

```jsonc
{
"providers": [
{
"type": "openai-compatible",
"name": "ollama",
"baseURL": "http://localhost:11434/v1"
}
],
"models": {
"planning": "ollama/llama3.1:70b",
"prose": "ollama/llama3.1:70b",
"fast": "ollama/llama3.1:8b",
"nano": "ollama/llama3.1:8b",
"embedding": "ollama/nomic-embed-text"
}
}
```

**Example: LM Studio**

```jsonc
{
"providers": [
{
"type": "openai-compatible",
"name": "lmstudio",
"baseURL": "http://localhost:1234/v1"
}
],
"models": {
"planning": "lmstudio/loaded-model",
"prose": "lmstudio/loaded-model",
"fast": "lmstudio/loaded-model",
"nano": "lmstudio/loaded-model",
"embedding": "lmstudio/loaded-model"
}
}
```

**Example: Custom API with authentication**

```jsonc
{
"providers": [
{
"type": "openai-compatible",
"name": "myapi",
"baseURL": "https://api.example.com/v1",
"apiKey": "your-api-key",
"headers": {
"X-Custom-Header": "value"
}
}
],
"models": {
"planning": "myapi/my-model",
"prose": "myapi/my-model",
"fast": "myapi/my-model",
"nano": "myapi/my-model",
"embedding": "myapi/my-model"
}
}
```

### Mixing Providers

You can combine native providers with custom endpoints:

```jsonc
{
"providers": [
{ "type": "openai" },
{
"type": "openai-compatible",
"name": "ollama",
"baseURL": "http://localhost:11434/v1"
}
],
"models": {
"planning": "openai/gpt-5.2",
"prose": "openai/gpt-5.2",
"fast": "ollama/llama3.1:8b",
"nano": "ollama/llama3.1:8b",
"embedding": "openai/text-embedding-3-small"
}
}
```

```bash
export OPENAI_API_KEY=sk-...
```

## Logs & UI

Expand Down
32 changes: 25 additions & 7 deletions src/agents/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { stepCountIs, ToolLoopAgent, type ToolSet } from "ai"
import { z } from "zod"
import type { Blackboard } from "../blackboard"
import type { SessionSummary } from "../blackboard/types"
import { config } from "../config"
import type { RuntimeConfig } from "../config"
import { createBlackboardTools } from "../tools/blackboard"
import type { CodebaseTools } from "../tools/codebase"
import type { DocTools } from "../tools/docs"
Expand Down Expand Up @@ -121,6 +121,7 @@ export function createOrchestratorAgent(
codebaseTools: Partial<CodebaseTools>
interactionTools: InteractionTools
},
runtimeConfig: RuntimeConfig,
) {
const systemPrompt = buildSystemPrompt(context)

Expand All @@ -147,10 +148,27 @@ export function createOrchestratorAgent(
...blackboardTools,
}

const researchAgent = createResearchAgent(blackboard, researchTools)
const plannerAgent = createPlannerAgent(blackboard, plannerTools)
const writerAgent = createWriterAgent(blackboard, writerTools)
const userAgent = createUserAgent(blackboard, userTools)
// Create sub-agents with injected config
const researchAgent = createResearchAgent(blackboard, researchTools, {
maxRetries: runtimeConfig.agents.runtime.research.maxRetries,
model: runtimeConfig.models.fast,
providerOptions: runtimeConfig.agents.runtime.research.providerOptions,
})
const plannerAgent = createPlannerAgent(blackboard, plannerTools, {
maxRetries: runtimeConfig.agents.runtime.planner.maxRetries,
model: runtimeConfig.models.fast,
providerOptions: runtimeConfig.agents.runtime.planner.providerOptions,
})
const writerAgent = createWriterAgent(blackboard, writerTools, {
maxRetries: runtimeConfig.agents.runtime.writer.maxRetries,
model: runtimeConfig.models.prose,
providerOptions: runtimeConfig.agents.runtime.writer.providerOptions,
})
const userAgent = createUserAgent(blackboard, userTools, {
maxRetries: runtimeConfig.agents.runtime.userInteraction.maxRetries,
model: runtimeConfig.models.fast,
providerOptions: runtimeConfig.agents.runtime.userInteraction.providerOptions,
})
Comment on lines +151 to +171
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Aqua-123 i'm assuming we should now specify provider options via docbot.config.json? same for fallback models? (which used to be both hardcoded in config.ts)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the flow of user config being loaded is that

  1. User configures provider + models in docbot.config.jsonc
{
  "providers": [
    {
      "type": "openai-compatible",
      "name": "lmstudio",              // ← this becomes the provider prefix
      "baseURL": "http://localhost:1234/v1"
    }
  ],
  "models": {
    "planning": "lmstudio/loaded-model",   // ← "lmstudio" + "loaded-model"
    "prose": "lmstudio/loaded-model",
    ...
  }
}
  1. initializeProviders() registers custom providers (src/config/providers/index.ts:34-46):
registerProviders(configs) {
  if (configs.length > 0) {
    this.useGateway = false  // ← switches OFF gateway mode
  }
  for (const config of configs) {
    if (config.type === "openai-compatible") {
      this.registerOpenAICompatible(config)  // ← registers "lmstudio" → factory
    }
  }
}
  1. getModel("lmstudio/loaded-model") resolves the model (index.ts:71-112):
getModel(modelId: string) {
  const providerName = "lmstudio"    // parsed from "lmstudio/loaded-model"
  const modelName = "loaded-model"

  // 1. Check custom providers first ← MATCHES HERE
  if (this.customProviders.has("lmstudio")) {
    return this.customProviders.get("lmstudio")!("loaded-model")
  }
  
  // 2. Native overrides (openai, anthropic, etc. with custom keys)
  // 3. Gateway mode (default, but disabled when providers are configured)
  // 4. Default native providers (uses env vars)
  // 5. Error if unknown
}
  1. The custom provider uses @ai-sdk/openai-compatible (openai-compatible.ts):
createOpenAICompatible({
  name: "lmstudio",
  baseURL: "http://localhost:1234/v1",
  apiKey: config.apiKey,  // optional
})

So what is not taken as proper config input is

  • maxRetries per agent
  • Provider-specific providerOptions (reasoning effort, thinking levels, etc.)
  • Gateway fallback model chains
  • Reranker model (Cohere)
  • Vector dimensions

Now we can determine which of these we want to add these configurable or not


const orchestratorTools = {
...createWorkflowTools(),
Expand Down Expand Up @@ -408,12 +426,12 @@ export function createOrchestratorAgent(
},
}

const runtime = config.agents.runtime.orchestrator
const runtime = runtimeConfig.agents.runtime.orchestrator

const agent = new ToolLoopAgent({
instructions: systemPrompt,
maxRetries: runtime.maxRetries,
model: config.models.planning,
model: runtimeConfig.models.planning,

prepareStep: ({ messages }) => {
// inject current session state at every step so the orchestrator
Expand Down
18 changes: 12 additions & 6 deletions src/agents/planner.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ProviderOptions } from "@ai-sdk/provider-utils"
import type { LanguageModel } from "ai"
import { stepCountIs, ToolLoopAgent, type ToolSet } from "ai"
import type { Blackboard } from "../blackboard"
import { config } from "../config"
import type { BlackboardTools } from "../tools/blackboard"
import type { DocTools } from "../tools/docs"
import { hasToolCall } from "./helpers"
Expand Down Expand Up @@ -108,20 +109,25 @@ export type PlannerAgentTools = DocTools &
| "submit_plan"
>

export interface PlannerAgentConfig {
model: LanguageModel
maxRetries: number
providerOptions?: ProviderOptions
}

/**
* create the planner agent that creates documentation plans
*/
export function createPlannerAgent(
_blackboard: Blackboard,
tools: PlannerAgentTools,
config: PlannerAgentConfig,
) {
const runtime = config.agents.runtime.planner

return new ToolLoopAgent({
instructions: PLANNER_SYSTEM_PROMPT, // mid-tier model for structure creation
maxRetries: runtime.maxRetries,
model: config.models.fast,
providerOptions: runtime.providerOptions,
maxRetries: config.maxRetries,
model: config.model,
providerOptions: config.providerOptions,
stopWhen: [
hasToolCall("submit_plan"),
stepCountIs(12), // safety net
Expand Down
Loading