import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";
import { tool } from "@langchain/core/tools";
import { StateGraph, START, END, Annotation } from "@langchain/langgraph";
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { z } from "zod";
// ── Routor-backed model ──────────────────────────────────────────
const model = new ChatOpenAI({
apiKey: process.env.ROUTOR_API_KEY!,
configuration: { baseURL: "https://api.routor.ai/v1" },
modelName: "auto",
});
// ── Tools ────────────────────────────────────────────────────────
const searchWeb = tool(
async ({ query }) => `Search results for: ${query}`,
{
name: "search_web",
description: "Search the web for up-to-date information.",
schema: z.object({ query: z.string() }),
}
);
const calculate = tool(
async ({ expression }) => {
try { return String(Function(`"use strict"; return (${expression})`)()); }
catch (e) { return `Error: ${e}`; }
},
{
name: "calculate",
description: "Evaluate a math expression and return the result.",
schema: z.object({ expression: z.string() }),
}
);
const tools = [searchWeb, calculate];
const modelWithTools = model.bindTools(tools);
// ── Graph state ──────────────────────────────────────────────────
const AgentState = Annotation.Root({
messages: Annotation<any[]>({ reducer: (a, b) => [...a, ...b] }),
});
async function agentNode(state: typeof AgentState.State) {
const response = await modelWithTools.invoke(state.messages);
return { messages: [response] };
}
function shouldContinue(state: typeof AgentState.State) {
const lastMessage = state.messages[state.messages.length - 1];
return lastMessage.tool_calls?.length ? "tools" : END;
}
// ── Build the graph ──────────────────────────────────────────────
const graph = new StateGraph(AgentState)
.addNode("agent", agentNode)
.addNode("tools", new ToolNode(tools))
.addEdge(START, "agent")
.addConditionalEdges("agent", shouldContinue, { tools: "tools", END })
.addEdge("tools", "agent")
.compile();
// ── Run it ───────────────────────────────────────────────────────
const result = await graph.invoke({
messages: [new HumanMessage(
"Search for the latest Claude Opus pricing, then calculate the monthly cost for 5 million input tokens."
)],
});
for (const msg of result.messages) {
console.log(`[${msg._getType()}] ${msg.content}`);
}