Skip to content
Draft
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
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
# LLM provider: "anthropic" (default) or "azure"
# LLM_PROVIDER=anthropic

# Required when LLM_PROVIDER = "anthropic" or unset
ANTHROPIC_API_KEY=

# Azure OpenAI (when LLM_PROVIDER = "azure")
# Example: HMS endpoint https://azure-ai.hms.edu
# If your server expects paths under /openai, use https://azure-ai.hms.edu/openai
AZURE_OPENAI_ENDPOINT=
AZURE_OPENAI_API_KEY=
AZURE_OPENAI_API_VERSION=2024-05-01-preview
AZURE_OPENAI_DEPLOYMENT=gpt-5
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ bower_components
build/Release

# Dependency directories
.pnpm-store/
node_modules/
jspm_packages/

Expand Down
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,16 @@ You can add your own graphs, biomedical or otherwise, without changing any code.
cp .env.example .env
```

Open `.env` and add your API key. The CLI currently uses [Anthropic](https://www.anthropic.com/) as its LLM provider:
Open `.env` and add credentials for your chosen LLM provider. The CLI supports [Anthropic](https://www.anthropic.com/) (default) and [Azure OpenAI](https://azure.microsoft.com/products/ai-services/openai-service).

**Anthropic (default):**

```env
ANTHROPIC_API_KEY=your_key_here
```

**Azure OpenAI:** set `LLM_PROVIDER=azure` and your Azure credentials (see [OpenAI (Azure)](#openai-azure) below).

4. **Start the CLI**:

```bash
Expand Down Expand Up @@ -83,6 +87,24 @@ Find the relationship between metformin and breast cancer.

The agent will search the knowledge graph, traverse relationships, and synthesize an answer while citing the specific nodes and edges it used.

### OpenAI (Azure)

To use Azure OpenAI (e.g. an institutional endpoint like HMS), set in `.env`:

```env
LLM_PROVIDER=azure
AZURE_OPENAI_ENDPOINT=https://azure-ai.hms.edu
AZURE_OPENAI_API_KEY=your_azure_key
AZURE_OPENAI_API_VERSION=2024-05-01-preview
AZURE_OPENAI_DEPLOYMENT=gpt-4o-1120
```

If your server expects paths under `/openai`, use `https://azure-ai.hms.edu/openai` as the endpoint.

### Switching models

The `/models` command is listed in the CLI help. Model selection is currently via environment: set `LLM_PROVIDER` to `anthropic` or `azure` and configure the corresponding API keys, then restart the CLI for the change to take effect. In-app model switching (e.g. choosing a model from a list when you type `/models`) will be supported when the TUI library adds custom command registration and transport invalidation.

## Adding Your Own Knowledge Graph

Adding a new graph takes four steps and requires **no code changes**.
Expand Down Expand Up @@ -182,7 +204,7 @@ Tool renderers provide rich visualization of tool outputs in the terminal. See `
- **Runtime**: [Bun](https://bun.sh/)
- **Language**: TypeScript
- **UI**: [React 19](https://react.dev/) with [@ai-tui/core](https://www.npmjs.com/package/@ai-tui/core)
- **LLM**: [Vercel AI SDK](https://sdk.vercel.ai/) (currently configured for Anthropic Claude)
- **LLM**: [Vercel AI SDK](https://sdk.vercel.ai/) (Anthropic Claude or Azure OpenAI via `LLM_PROVIDER`)
- **Data**: Local parquet files queried via [DuckDB](https://duckdb.org/)
- **Validation**: [Zod](https://zod.dev/)

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"dependencies": {
"@ai-sdk/anthropic": "^3.0.17",
"@ai-sdk/azure": "^3.0.0",
"@ai-sdk/react": "^3.0.41",
"@ai-tui/core": "^0.1.1",
"@duckdb/node-api": "1.4.4-r.1",
Expand Down
49 changes: 49 additions & 0 deletions pnpm-lock.yaml

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

77 changes: 70 additions & 7 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createAnthropic } from "@ai-sdk/anthropic";
import { createAzure } from "@ai-sdk/azure";
import {
Agent,
type ConfigInput,
Expand All @@ -16,21 +17,77 @@ import { GraphLoader, makeParquetGraphTools } from "./parquet-tools/index.ts";
import { graphAgentPrompt, regularPrompt } from "./prompts.ts";
import { GetNodeDetailsTool } from "./tool-renderers/index.ts";

export const env = createEnv({
const providerSchema = z.enum(["anthropic", "azure"]);

const rawEnv = createEnv({
server: {
ANTHROPIC_API_KEY: z.string().min(1),
LLM_PROVIDER: z
.string()
.optional()
.transform((v) => (v ? providerSchema.parse(v) : "anthropic")),
ANTHROPIC_API_KEY: z.string().optional(),
AZURE_OPENAI_ENDPOINT: z.string().url().optional(),
AZURE_OPENAI_API_KEY: z.string().optional(),
AZURE_OPENAI_API_VERSION: z
.string()
.optional()
.default("2024-05-01-preview"),
AZURE_OPENAI_DEPLOYMENT: z.string().optional().default("gpt-4o-1120"),
},
runtimeEnv: process.env,
emptyStringAsUndefined: true,
});

function validateEnv() {
const provider = rawEnv.LLM_PROVIDER;
if (provider === "anthropic") {
if (!rawEnv.ANTHROPIC_API_KEY?.trim()) {
throw new Error(
"ANTHROPIC_API_KEY is required when LLM_PROVIDER is anthropic (or unset). Set it in .env",
);
}
return {
...rawEnv,
ANTHROPIC_API_KEY: rawEnv.ANTHROPIC_API_KEY!,
};
}
// provider === "azure"
if (!rawEnv.AZURE_OPENAI_ENDPOINT?.trim() || !rawEnv.AZURE_OPENAI_API_KEY?.trim()) {
throw new Error(
"AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY are required when LLM_PROVIDER=azure. Set them in .env",
);
}
return {
...rawEnv,
AZURE_OPENAI_ENDPOINT: rawEnv.AZURE_OPENAI_ENDPOINT!,
AZURE_OPENAI_API_KEY: rawEnv.AZURE_OPENAI_API_KEY!,
};
}

export const env = validateEnv();

// Discover graph metadata instantly; parquet data loads lazily per agent
const DATA_DIR = join(import.meta.dir, "..", "data");
const loader = new GraphLoader(DATA_DIR);

const anthropic = createAnthropic({
apiKey: env.ANTHROPIC_API_KEY,
});
const anthropic =
env.LLM_PROVIDER === "anthropic"
? createAnthropic({ apiKey: env.ANTHROPIC_API_KEY })
: null;

const azure =
env.LLM_PROVIDER === "azure"
? createAzure({
baseURL: env.AZURE_OPENAI_ENDPOINT,
apiKey: env.AZURE_OPENAI_API_KEY,
apiVersion: env.AZURE_OPENAI_API_VERSION,
})
: null;

const currentModelDisplay =
env.LLM_PROVIDER === "azure"
? { providerName: "OpenAI", name: `Azure (${env.AZURE_OPENAI_DEPLOYMENT})` }
: { providerName: "Anthropic", name: "Claude Opus 4.5" };

/**
* Custom tool component renderers for graph agent tools.
Expand All @@ -46,7 +103,7 @@ const configValue: ConfigInput = {
new Agent({
id: meta.slug,
name: meta.name,
model: { providerName: "Anthropic", name: "Claude Opus 4.5" },
model: currentModelDisplay,
color: meta.color as HexColor,
toolComponents: graphToolComponents,
createTransport: async ({ transportOptions }) => {
Expand All @@ -55,8 +112,13 @@ const configValue: ConfigInput = {
loader,
)) as ToolSet;

const model =
env.LLM_PROVIDER === "azure" && azure
? azure(env.AZURE_OPENAI_DEPLOYMENT)
: anthropic!("claude-opus-4-5");

const agent = new ToolLoopAgent({
model: anthropic("claude-opus-4-5"),
model,
tools: graphTools,
instructions: `${regularPrompt}\n\n${graphAgentPrompt}`,
stopWhen: stepCountIs(50),
Expand All @@ -69,6 +131,7 @@ const configValue: ConfigInput = {
},
}),
) as ConfigInput["agents"],
commands: [{ name: "/models", hint: "Switch LLM model (set LLM_PROVIDER and restart)" }],
appName: {
sections: [
{
Expand Down