langchain
Version:
Typescript bindings for langchain
90 lines (89 loc) • 3.55 kB
JavaScript
const require_chat_models_universal = require("../../chat_models/universal.cjs");
const require_middleware = require("../middleware.cjs");
let _langchain_core_messages = require("@langchain/core/messages");
let zod_v4 = require("zod/v4");
let _langchain_core_tracers_tracer_langchain = require("@langchain/core/tracers/tracer_langchain");
// src/agents/middleware/modelRouter.ts
const QUESTION_ID = "model_route";
/**
* Select an agent's model with a decision model's `Choice` answer (beta).
*
* The middleware classifies the latest human message once before an agent
* run, stores the complete `ChoiceAnswer` in agent state as `modelRoute`, and
* uses its selected label for every model call in the run. Keeping the
* complete answer makes probabilities and confidence available in state and
* traces. Classifier failures propagate and terminate the run rather than
* silently selecting a different model.
*
* @example
* ```ts
* import { createAgent, modelRouterMiddleware } from "langchain";
*
* const router = modelRouterMiddleware({
* choices: {
* fast: {
* model: "openai:gpt-5-mini",
* criteria: "Simple, well-scoped tasks.",
* },
* powerful: {
* model: powerfulModel,
* criteria: "Complex tasks requiring deeper reasoning.",
* },
* },
* instructions: "Choose the least costly model suited to the task.",
* classifier,
* });
* const agent = createAgent({
* model: "openai:gpt-5-mini",
* middleware: [router],
* });
* ```
*
* @throws Error if `choices` is empty.
*/
function modelRouterMiddleware(config) {
const { choices, instructions, classifier } = config;
const routes = Object.keys(choices);
if (routes.length === 0) throw new Error("modelRouterMiddleware requires at least one choice.");
const questions = { [QUESTION_ID]: {
type: "choice",
instructions,
criteria: Object.fromEntries(routes.map((route) => [route, choices[route].criteria]))
} };
const models = /* @__PURE__ */ new Map();
const modelFor = (route) => {
let pending = models.get(route);
if (pending === void 0) {
const { model } = choices[route];
pending = Promise.resolve(typeof model === "string" ? require_chat_models_universal.initChatModel(model) : model);
pending.catch(() => models.delete(route));
models.set(route, pending);
}
return pending;
};
return require_middleware.createMiddleware({
name: "ModelRouterMiddleware",
stateSchema: zod_v4.z.object({ modelRoute: zod_v4.z.custom().optional() }),
tracePolicy: { processInputs: _langchain_core_tracers_tracer_langchain.omitPayload },
async beforeAgent(state, runtime) {
const message = [...state.messages].reverse().find((candidate) => _langchain_core_messages.HumanMessage.isInstance(candidate));
if (message === void 0) throw new Error("modelRouterMiddleware requires a HumanMessage in the agent state to route on.");
const answer = (await classifier.invoke({
state: message,
questions
}, { signal: runtime.signal })).choices[QUESTION_ID];
if (!Object.hasOwn(choices, answer.choice)) throw new Error(`modelRouterMiddleware: the classifier selected unknown route "${answer.choice}".`);
return { modelRoute: answer };
},
async wrapModelCall(request, handler) {
const route = request.state.modelRoute;
if (route === void 0) throw new Error("modelRouterMiddleware found no routing answer in state.");
return handler({
...request,
model: await modelFor(route.choice)
});
}
});
}
exports.modelRouterMiddleware = modelRouterMiddleware;
//# sourceMappingURL=modelRouter.cjs.map