Skip to content

Complex models

An executor can route to several targets, enrich a request, and preserve both streaming and non-streaming behavior. Keep this logic in the model source, not in client applications.

complex-model.ts
import { prependSystemPrompt } from "@neutrome/lil-engine";
import type { Executor } from "@neutrome/lilsdk";
import { fallback, retry } from "@neutrome/lilsdk/loops";
const upstream = fallback([
retry("openai/gpt-5", { attempts: 2 }),
"openai/gpt-5-mini",
]);
const executor: Executor = {
async execute(request, ctx) {
return ctx.invoke(
upstream,
prependSystemPrompt(request, "Answer clearly and cite uncertainty."),
);
},
async *stream(request, ctx) {
yield* ctx.invokeStream(
upstream,
prependSystemPrompt(request, "Answer clearly and cite uncertainty."),
);
},
};
export default executor;

prependSystemPrompt() returns a new program. It does not mutate the client request. retry() retries only before a stream emits content; fallback() tries the next target only when the prior target fails before emitting content.

  • Use provider-qualified targets such as openai/gpt-5.
  • Use another workspace model ID when composing your own models.
  • Keep execute and stream behavior equivalent unless the model is intentionally streaming-only.
  • Attach tools in the dashboard; a deployed attachment owns the tool loop.

Use the loops reference and engine reference when you need the complete option contracts.