diff --git a/demo/inngest.ts b/demo/inngest.ts index 7e5940e..a0e9864 100644 --- a/demo/inngest.ts +++ b/demo/inngest.ts @@ -1,13 +1,11 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { - anthropic, createAgent, createNetwork, createTool, defaultRoutingAgent, - openai, -} from "../src/index"; -import { EventSchemas, Inngest } from "inngest"; +} from "@inngest/agent-kit"; +import { EventSchemas, Inngest, openai } from "inngest"; import { z } from "zod"; export const inngest = new Inngest({ @@ -24,26 +22,12 @@ export const inngest = new Inngest({ export const fn = inngest.createFunction( { id: "agent" }, { event: "agent/run" }, - async ({ event, step }) => { - const model = openai({ model: "gpt-4", step }); - + async ({ event }) => { // 1. Single agent - // Run a single agent as a prompt without a network. - await codeWritingAgent.run(event.data.input, { - model, - }); + await codeWritingAgent.run(event.data.input, { model }); // 2. A network of agents that works together - const network = createNetwork({ - agents: [ - codeWritingAgent.withModel(model), - executingAgent.withModel(model), - ], - defaultModel: model, - maxIter: 4, - }); - // This uses the defaut agentic router to determine which agent to handle first. You can // optionally specifiy the agent that should execute first, and provide your own logic for // handling logic in between agent calls. @@ -57,9 +41,11 @@ export const fn = inngest.createFunction( }); return result; - }, + } ); +const model = openai({ model: "gpt-4" }); + const systemPrompt = "You are an expert TypeScript programmer. You can create files with idiomatic TypeScript code, with comments and associated tests."; @@ -109,7 +95,7 @@ const codeWritingAgent = createAgent({ filename: z.string(), content: z.string(), }) - .required(), + .required() ), }) .required(), @@ -157,3 +143,12 @@ Think carefully about the request that the user is asking for. Do not respond wi `, }); + +const network = createNetwork({ + agents: [ + codeWritingAgent.withModel(model), + executingAgent.withModel(model), + ], + defaultModel: model, + maxIter: 4, +}); diff --git a/demo/mw.ts b/demo/mw.ts index fbedc39..1faebe7 100644 --- a/demo/mw.ts +++ b/demo/mw.ts @@ -1,5 +1,5 @@ -import { agenticOpenai, createAgent, createNetwork } from "@inngest/agent-kit"; -import { InngestMiddleware, type OpenAi } from "inngest"; +import { createAgent, createNetwork } from "@inngest/agent-kit"; +import { InngestMiddleware, openai, type OpenAi } from "inngest"; export const codeWritingNetworkMiddleware = ( defaultModelOptions: OpenAi.AiModelOptions, @@ -7,14 +7,16 @@ export const codeWritingNetworkMiddleware = ( return new InngestMiddleware({ name: "Code Writing Agent Middleware", init() { + const model = openai({ ...defaultModelOptions }); + return { onFunctionRun() { return { - transformInput({ ctx: { step } }) { + transformInput() { const codeWritingNetwork = createNetwork({ agents: [codeWritingAgent, executingAgent], maxIter: 4, - defaultModel: agenticOpenai({ ...defaultModelOptions, step }), + defaultModel: model, }); return { diff --git a/demo/package.json b/demo/package.json index 62e5aa7..667e695 100644 --- a/demo/package.json +++ b/demo/package.json @@ -9,9 +9,9 @@ "license": "ISC", "description": "", "dependencies": { - "@inngest/agent-kit": "~0.0.1", + "@inngest/agent-kit": "file:../inngest-agent-kit-0.0.3.tgz", "express": "^4.21.1", - "inngest": "^3.27.3" + "inngest": "^3.27.6-pr-776.2" }, "devDependencies": { "@types/express": "^5.0.0", diff --git a/package.json b/package.json index a322cbc..a0f8afb 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,8 @@ } }, "dependencies": { - "inngest": "^3.27.4", "express": "^4.21.1", + "inngest": "3.27.6-pr-776.2", "openai-zod-to-json-schema": "^1.0.3", "zod": "^3.23.8" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61a626b..25f6a98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^4.21.1 version: 4.21.1 inngest: - specifier: ^3.27.4 - version: 3.27.4(express@4.21.1)(typescript@5.7.2) + specifier: 3.27.6-pr-776.2 + version: 3.27.6-pr-776.2(express@4.21.1)(typescript@5.7.2) openai-zod-to-json-schema: specifier: ^1.0.3 version: 1.0.3(zod@3.23.8) @@ -863,8 +863,8 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - inngest@3.27.4: - resolution: {integrity: sha512-S71jJNxmfA9d4jmFKSxgi/u5d+tSmpsThAmTMHhjGieBTSrfGguwhHHskkPpFCtrMWI+Qdy8ek/rtQNxFkE9eQ==} + inngest@3.27.6-pr-776.2: + resolution: {integrity: sha512-hod0BjJkKKSuA5hyIwMuPqaVBKM4UY9zZwU7kJPV+BCmhEOf0oX0Rr4x5eLffjwTzCXWWi4HuNtBXZpmNF58tQ==} engines: {node: '>=14'} peerDependencies: '@sveltejs/kit': '>=1.27.3' @@ -2467,7 +2467,7 @@ snapshots: inherits@2.0.4: {} - inngest@3.27.4(express@4.21.1)(typescript@5.7.2): + inngest@3.27.6-pr-776.2(express@4.21.1)(typescript@5.7.2): dependencies: '@types/debug': 4.1.12 canonicalize: 1.0.8 diff --git a/src/adapters/index.ts b/src/adapters/index.ts new file mode 100644 index 0000000..1e80fc3 --- /dev/null +++ b/src/adapters/index.ts @@ -0,0 +1,22 @@ +import { type AiAdapter, type AiAdapters } from "inngest"; +import { type AgenticModel } from "../model"; +import * as openai from "./openai"; +import * as anthropic from "./anthropic"; + +export type Adapters = { + [Format in AiAdapter.Format]: { + request: AgenticModel.RequestParser; + response: AgenticModel.ResponseParser; + }; +}; + +export const adapters: Adapters = { + "openai-chat": { + request: openai.requestParser, + response: openai.responseParser, + }, + anthropic: { + request: anthropic.requestParser, + response: anthropic.responseParser, + }, +}; diff --git a/src/agent.ts b/src/agent.ts index b984386..e0eeb09 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -1,4 +1,5 @@ -import { type AgenticModel } from "./model"; +import { type AiAdapter } from "inngest"; +import { createAgenticModelFromAiAdapter, type AgenticModel } from "./model"; import { type Network } from "./network"; import { type State, @@ -11,7 +12,7 @@ import { type ResultLifecycleArgs, type Tool, } from "./types"; -import { type AnyZodType, type MaybePromise } from "./util"; +import { getStepTools, type AnyZodType, type MaybePromise } from "./util"; /** * createTool is a helper that properly types the input argument for a handler @@ -72,15 +73,15 @@ export class Agent { this.assistant = opts.assistant || ""; this.tools = new Map(); this.lifecycles = opts.lifecycle; - this.model = opts.model; + this.model = opts.model && createAgenticModelFromAiAdapter(opts.model); for (const tool of opts.tools || []) { this.tools.set(tool.name, tool); } } - withModel(model: AgenticModel.Any): Agent { - this.model = model; + withModel(model: AiAdapter.Any): Agent { + this.model = createAgenticModelFromAiAdapter(model); return this; // for chaining } @@ -97,7 +98,11 @@ export class Agent { maxIter = 0, }: Agent.RunOptions | undefined = {}, ): Promise { - const p = model || this.model || network?.defaultModel; + const p = + (model && createAgenticModelFromAiAdapter(model)) || + this.model || + (network?.defaultModel && + createAgenticModelFromAiAdapter(network.defaultModel)); if (!p) { throw new Error("No step caller provided to agent"); } @@ -226,7 +231,7 @@ export class Agent { const result = await found.handler(tool.input, { agent: this, network, - step: p.step, + step: await getStepTools(), }); // TODO: handle error and send them back to the LLM @@ -288,11 +293,11 @@ export namespace Agent { assistant?: string; tools?: Tool.Any[]; lifecycle?: Lifecycle; - model?: AgenticModel.Any; + model?: AiAdapter.Any; } export interface RunOptions { - model?: AgenticModel.Any; + model?: AiAdapter.Any; network?: Network; /** * State allows you to pass custom state into a single agent run call. This should only diff --git a/src/error.ts b/src/error.ts new file mode 100644 index 0000000..188b78d --- /dev/null +++ b/src/error.ts @@ -0,0 +1,6 @@ +export class AIGatewayError extends Error { + constructor(message: string) { + super(message); + this.name = "AIGatewayError"; + } +} diff --git a/src/index.ts b/src/index.ts index fce39d1..1b32a09 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,8 +5,3 @@ export * from "./network"; export * from "./state"; export * from "./types"; export * from "./util"; - -// Models -export * from "./models/gemini"; -export * from "./models/openai"; -export * from "./models/anthropic"; diff --git a/src/model.ts b/src/model.ts index dd5198b..b8aaad7 100644 --- a/src/model.ts +++ b/src/model.ts @@ -1,22 +1,36 @@ -import { type AiAdapter, type GetStepTools, type Inngest } from "inngest"; +import { type AiAdapter } from "inngest"; import { type InternalNetworkMessage } from "./state"; import { type Tool } from "./types"; +import { getStepTools } from "./util"; +import { adapters } from "./adapters"; + +export const createAgenticModelFromAiAdapter = < + TAiAdapter extends AiAdapter.Any, +>( + adapter: TAiAdapter, +): AgenticModel => { + const opts = adapters[adapter.format as AiAdapter.Format]; + + return new AgenticModel({ + model: adapter, + requestParser: + opts.request as unknown as AgenticModel.RequestParser, + responseParser: + opts.response as unknown as AgenticModel.ResponseParser, + }); +}; export class AgenticModel { #model: TAiAdapter; - - step: GetStepTools; requestParser: AgenticModel.RequestParser; responseParser: AgenticModel.ResponseParser; constructor({ model, - step, requestParser, responseParser, }: AgenticModel.Constructor) { this.#model = model; - this.step = step; this.requestParser = requestParser; this.responseParser = responseParser; } @@ -26,7 +40,9 @@ export class AgenticModel { input: InternalNetworkMessage[], tools: Tool.Any[], ): Promise { - const result = (await this.step.ai.infer(stepID, { + const step = await getStepTools(); + + const result = (await step.ai.infer(stepID, { model: this.#model, body: this.requestParser(this.#model, input, tools), })) as AiAdapter.Input; @@ -50,7 +66,6 @@ export namespace AgenticModel { export interface Constructor { model: TAiAdapter; - step: GetStepTools; requestParser: RequestParser; responseParser: ResponseParser; } diff --git a/src/models/README.md b/src/models/README.md deleted file mode 100644 index c425c1b..0000000 --- a/src/models/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Models - -These are agentic models supported by the library, often wrapping existing -models within `inngest`. diff --git a/src/models/anthropic.ts b/src/models/anthropic.ts deleted file mode 100644 index db57042..0000000 --- a/src/models/anthropic.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - anthropic as ianthropic, - type GetStepTools, - type Inngest, - type Anthropic, -} from "inngest"; -import { requestParser, responseParser } from "../adapters/anthropic"; -import { AgenticModel } from "../model"; - -export namespace AnthropicModel { - export interface Options - extends Omit { - /** - * The Anthropic model to use. - */ - model: Anthropic.AiModelOptions["model"] | TAiAdapter; - - /** - * The step tools to use internally within this model. - */ - step: GetStepTools; - } -} - -/** - * Create an agentic Anthropic model using the Anthropic chat format. - */ -export const anthropic = ({ - step, - ...modelOptions -}: AnthropicModel.Options) => { - const model = - typeof modelOptions.model === "string" - ? ianthropic({ ...modelOptions, model: modelOptions.model }) - : modelOptions.model; - - return new AgenticModel({ - model, - step, - requestParser, - responseParser, - }); -}; diff --git a/src/models/gemini.ts b/src/models/gemini.ts deleted file mode 100644 index 41201f7..0000000 --- a/src/models/gemini.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { - gemini as igemini, - type Gemini, - type GetStepTools, - type Inngest, -} from "inngest"; -import { requestParser, responseParser } from "../adapters/openai"; -import { AgenticModel } from "../model"; - -export namespace AgenticGeminiModel { - export interface Options - extends Omit { - /** - * The Gemini model to use. - */ - model: Gemini.AiModelOptions["model"] | TAiAdapter; - - /** - * The step tools to use internally within this model. - */ - step: GetStepTools; - } -} - -/** - * Create an agentic Gemini model using the OpenAI chat format. - * - * By default it targets the `https://generativelanguage.googleapis.com/v1beta/` - * base URL. - */ -export const gemini = ({ - step, - ...modelOptions -}: AgenticGeminiModel.Options) => { - const model = - typeof modelOptions.model === "string" - ? igemini({ ...modelOptions, model: modelOptions.model }) - : modelOptions.model; - - return new AgenticModel({ - model, - step, - requestParser, - responseParser, - }); -}; diff --git a/src/models/openai.ts b/src/models/openai.ts deleted file mode 100644 index a01c715..0000000 --- a/src/models/openai.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { openai as iopenai, type OpenAi } from "inngest"; -import { requestParser, responseParser } from "../adapters/openai"; -import { AgenticModel } from "../model"; -import { type AnyStepTools } from "../types"; - -export namespace AgenticOpenAiModel { - export interface Options - extends Omit { - /** - * The OpenAI model to use. - */ - model: OpenAi.AiModelOptions["model"] | TAiAdapter; - - /** - * The step tools to use internally within this model. - */ - step: AnyStepTools; - } -} - -/** - * Create an agentic OpenAI model using the OpenAI chat format. - * - * By default it targets the `https://api.openai.com/v1/` base URL. - */ -export const openai = ({ - step, - ...modelOptions -}: AgenticOpenAiModel.Options) => { - const model = - typeof modelOptions.model === "string" - ? iopenai({ ...modelOptions, model: modelOptions.model }) - : modelOptions.model; - - return new AgenticModel({ - model, - step, - requestParser, - responseParser, - }); -}; diff --git a/src/network.ts b/src/network.ts index 19e1e46..7f93d8e 100644 --- a/src/network.ts +++ b/src/network.ts @@ -1,8 +1,8 @@ import { z } from "zod"; import { Agent, createTool } from "./agent"; -import { type AgenticModel } from "./model"; import { type InferenceResult, State } from "./state"; import { type MaybePromise } from "./util"; +import { type AiAdapter } from "inngest"; /** * Network represents a network of agents. @@ -28,7 +28,7 @@ export class Network { * override an agent's specific model if the agent already has a model defined * (eg. via withModel or via its constructor). */ - defaultModel?: AgenticModel.Any; + defaultModel?: AiAdapter.Any; /** * maxIter is the maximum number of times the we can call agents before ending @@ -307,7 +307,7 @@ Follow the set of instructions: export namespace Network { export type Constructor = { agents: Agent[]; - defaultModel?: AgenticModel.Any; + defaultModel?: AiAdapter.Any; maxIter?: number; // state is any pre-existing network state to use in this Network instance. By // default, new state is created without any history for every Network. diff --git a/src/types.ts b/src/types.ts index 4b310e5..0d479ad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,3 @@ -import { type GetStepTools, type Inngest } from "inngest"; import { type output as ZodOutput } from "zod"; import { type Agent } from "./agent"; import { type Network } from "./network"; @@ -9,6 +8,7 @@ import { type MaybePromise, type SimplifyDeep, } from "./util"; +import { type GetStepTools, type Inngest } from "inngest"; export type Tool = { name: string; diff --git a/src/util.ts b/src/util.ts index 1a6dd35..6a0816f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,3 +1,4 @@ +import { getAsyncCtx } from "inngest/experimental"; import { type ZodType } from "zod"; export type MaybePromise = T | Promise; @@ -27,6 +28,19 @@ export const stringifyError = (e: unknown): string => { return String(e); }; +/** + * Attempts to retrieve the step tools from the async context. If the context is + * not found, an error is thrown. + */ +export const getStepTools = async () => { + const asyncCtx = await getAsyncCtx(); + if (!asyncCtx) { + throw new Error("Could not find Inngest step tooling in async context"); + } + + return asyncCtx.ctx.step; +}; + /** * Given an object `T`, return a new object where all keys with function types * as values are genericized. If the value is an object, recursively apply this