Skip to content

Commit

Permalink
Nits (#2)
Browse files Browse the repository at this point in the history
* Change `instructions` to `prompt`, update lifecycle names

* Make default provider optional in network

* Auto-format zod schema to json schema in OpenAI provider

* InferenceResult.instructions -> .system type change

* Add step to tool handler func

* Demo updates

* Deps fix

* Type fixes

* Remove old ESLint config

* Lint

* Lint

* Fix demos; implement APIs from demos

* Fix Zod typing

* Use main `inngest`

* Remove unused pieces from demo

* Fix up middleware example

---------

Co-authored-by: Jack Williams <[email protected]>
Co-authored-by: Jack Williams <[email protected]>
  • Loading branch information
3 people authored Nov 21, 2024
1 parent 5c78a6a commit 85b8f33
Show file tree
Hide file tree
Showing 13 changed files with 457 additions and 879 deletions.
52 changes: 0 additions & 52 deletions .eslintrc.js

This file was deleted.

140 changes: 105 additions & 35 deletions demo/inngest.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,144 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
createAgent,
createAgenticOpenAiProvider,
createNetwork,
createTypedTool,
defaultRoutingAgent,
} from "@inngest/agent-kit";
import { Inngest, openai } from "inngest";

export const inngest = new Inngest({ id: "agents" });
import { EventSchemas, Inngest, openai } from "inngest";
import { z } from "zod";

export const inngest = new Inngest({
id: "agents",
schemas: new EventSchemas().fromZod({
"agent/run": {
data: z.object({
input: z.string(),
}),
},
}),
});

export const fn = inngest.createFunction(
{ id: "agent" },
{ event: "agent/run" },
async ({ event, step }) => {
const provider = createAgenticOpenAiProvider({
provider: openai({ model: "gpt-3.5-turbo" }),
provider: openai({ model: "gpt-4" }),
step,
});

// 1. Single agents
//
// Run a single agent as a prompt without a network.
// const { output, raw } = await CodeWritingAgent.run(event.data.input, { provider });
const { output, raw } = await codeWritingAgent.run(event.data.input, {
provider,
});

// 2. Networks of agents
const cheapProvider = createAgenticOpenAiProvider({
provider: openai({ model: "gpt-3.5-turbo" }),
step,
});

const network = createNetwork({
agents: [CodeWritingAgent, ExecutingAgent],
agents: [
codeWritingAgent.withProvider(provider),
executingAgent.withProvider(cheapProvider),
],
defaultProvider: provider,
maxIter: 4,
});
// code -> executing -> code

// This uses the defaut agentic router to determine which agent to handle first. You can
// optinoally specifiy the agent that should execute first, and provide your own logic for
// handling logic in between agent calls.
const result = await network.run(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
event.data.input as string,
() => {
return defaultRoutingAgent.withProvider(provider);
},
);
const result = await network.run(event.data.input, ({ network }) => {
if (network.state.kv.has("files")) {
// Okay, we have some files. Did an agent run tests?
return executingAgent;
}

return defaultRoutingAgent.withProvider(provider);
});

return result;
},
);

const CodeWritingAgent = createAgent({
name: "Code writing agent",
description: "Writes TypeScript code and tests based off of a given input.",

lifecycle: {
afterInfer: ({ call }) => {
// Does this contain a solution?
// TODO: Parse filenames out of content.
return call;
},
const systemPrompt =
"You are an expert TypeScript programmer. Given a set of asks, think step-by-step to plan clean, " +
"idiomatic TypeScript code, with comments and tests as necessary.";

const codeWritingAgent = createAgent({
name: "Code writer",
// description helps LLM routers choose the right agents to run.
description: "An expert TypeScript programmer which can write and debug code",
// system defines a system prompt generated each time the agent is called by a network.
system: (network) => {
if (!network) {
return systemPrompt;
}

// Each time this agent runs, it may produce "file" content. Check if any
// content has already been produced in an agentic workflow.
const files = network.state.kv.get<Record<string, string>>("files");

if (files === undefined) {
// Use the default system prompt.
return systemPrompt;
}

// There are files present in the network's state, so add them to the promp to help
// provide previous context automatically.
let prompt = systemPrompt + "The following code already exists:";
for (const [name, contents] of Object.entries(files)) {
prompt += `<file name='${name}'>${contents}</file>`;
}

return prompt;
},

instructions: `You are an expert TypeScript engineer who excels at test-driven-development. Your primary focus is to take system requirements and write unit tests for a set of functions.
Think carefully about the request that the user is asking for. Do not respond with anything else other than the following XML tags:
- If you would like to write code, add all code within the following tags (replace $filename and $contents appropriately):
<file name="$filename.ts">
$contents
</file>
`,
tools: [
// This tool forces the model to generate file content as structured data. Other options
// are to use XML tags in a prompt, eg:
// "Do not respond with anything else other than the following XML tags:" +
// "- If you would like to write code, add all code within the following tags (replace $filename and $contents appropriately):" +
// " <file name='$filename.ts'>$contents</file>";
createTypedTool({
name: "write_files",
description: "Write code with the given filenames",
parameters: z
.object({
files: z.array(
z
.object({
filename: z.string(),
content: z.string(),
})
.required(),
),
})
.required(),
handler: (output, { network }) => {
// files is the output from the model's response in the format above.
// Here, we store OpenAI's generated files in the response.
const files =
network?.state.kv.get<Record<string, string>>("files") || {};

for (const file of output.files) {
files[file.filename] = file.content;
}

network?.state.kv.set<Record<string, string>>("files", files);
},
}),
],
});

const ExecutingAgent = createAgent({
const executingAgent = createAgent({
name: "Test execution agent",
description: "Executes written TypeScript tests",

Expand All @@ -79,7 +149,7 @@ const ExecutingAgent = createAgent({
},
},

instructions: `You are an export TypeScript engineer that can execute commands, run tests, debug the output, and make modifications to code.
system: `You are an export TypeScript engineer that can execute commands, run tests, debug the output, and make modifications to code.
Think carefully about the request that the user is asking for. Do not respond with anything else other than the following XML tags:
Expand Down
42 changes: 35 additions & 7 deletions demo/inngest_alt.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,51 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { defaultRoutingAgent } from "@inngest/agent-kit";
import { Inngest } from "inngest";
import { codeWritingAgentMiddleware } from "./mw";
import { EventSchemas, Inngest } from "inngest";
import { z } from "zod";
import { codeWritingNetworkMiddleware } from "./mw";

export const inngest = new Inngest({
id: "agents",
middleware: [codeWritingAgentMiddleware({ model: "gpt-3.5-turbo" })],
schemas: new EventSchemas().fromZod({
"agent/run": {
data: z.object({
input: z.string(),
}),
},
}),
middleware: [codeWritingNetworkMiddleware({ model: "gpt-3.5-turbo" })],
});

export const fn = inngest.createFunction(
{ id: "agent" },
{ event: "agent/run" },
async ({ event, codeWritingNetwork }) => {
async ({
event,
ai: {
agents: { codeWritingAgent, executingAgent },
networks: { codeWritingNetwork },
},
}) => {
// 1. Single agents
//
// Run a single agent as a prompt without a network.
const { output, raw } = await codeWritingAgent.run(event.data.input);

// 2. Networks of agents
//
// This uses the defaut agentic router to determine which agent to handle first. You can
// optinoally specifiy the agent that should execute first, and provide your own logic for
// handling logic in between agent calls.
const result = await codeWritingNetwork.run(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
event.data.input as string,
() => defaultRoutingAgent,
event.data.input,
({ network }) => {
if (network.state.kv.has("files")) {
// Okay, we have some files. Did an agent run tests?
return executingAgent;
}

return defaultRoutingAgent;
},
);

return result;
Expand Down
30 changes: 20 additions & 10 deletions demo/mw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ import {
} from "@inngest/agent-kit";
import { InngestMiddleware, openai, type OpenAiProviderOptions } from "inngest";

export const codeWritingAgentMiddleware = (opts: OpenAiProviderOptions) => {
export const codeWritingNetworkMiddleware = (
defaultProviderOptions: OpenAiProviderOptions,
) => {
return new InngestMiddleware({
name: "Code Writing Agent Middleware",
init() {
const provider = openai(opts);
const provider = openai(defaultProviderOptions);

return {
onFunctionRun() {
return {
transformInput({ ctx: { step } }) {
const codeWritingNetwork = createNetwork({
agents: [CodeWritingAgent, ExecutingAgent],
agents: [codeWritingAgent, executingAgent],
maxIter: 4,
defaultProvider: createAgenticOpenAiProvider({
provider,
Expand All @@ -26,7 +28,15 @@ export const codeWritingAgentMiddleware = (opts: OpenAiProviderOptions) => {

return {
ctx: {
codeWritingNetwork,
ai: {
agents: {
codeWritingAgent,
executingAgent,
},
networks: {
codeWritingNetwork,
},
},
},
};
},
Expand All @@ -37,19 +47,19 @@ export const codeWritingAgentMiddleware = (opts: OpenAiProviderOptions) => {
});
};

const CodeWritingAgent = createAgent({
const codeWritingAgent = createAgent({
name: "Code writing agent",
description: "Writes TypeScript code and tests based off of a given input.",

lifecycle: {
afterInfer: ({ call }) => {
onResponse: ({ result }) => {
// Does this contain a solution?
// TODO: Parse filenames out of content.
return call;
return result;
},
},

instructions: `You are an expert TypeScript engineer who excels at test-driven-development. Your primary focus is to take system requirements and write unit tests for a set of functions.
system: `You are an expert TypeScript engineer who excels at test-driven-development. Your primary focus is to take system requirements and write unit tests for a set of functions.
Think carefully about the request that the user is asking for. Do not respond with anything else other than the following XML tags:
Expand All @@ -61,7 +71,7 @@ const CodeWritingAgent = createAgent({
`,
});

const ExecutingAgent = createAgent({
const executingAgent = createAgent({
name: "Test execution agent",
description: "Executes written TypeScript tests",

Expand All @@ -72,7 +82,7 @@ const ExecutingAgent = createAgent({
},
},

instructions: `You are an export TypeScript engineer that can execute commands, run tests, debug the output, and make modifications to code.
system: `You are an export TypeScript engineer that can execute commands, run tests, debug the output, and make modifications to code.
Think carefully about the request that the user is asking for. Do not respond with anything else other than the following XML tags:
Expand Down
11 changes: 8 additions & 3 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ export default tseslint.config(
eslintPluginPrettierRecommended,
{
rules: {
"prettier/prettier": "warn",
"@typescript-eslint/no-namespace": "off",
}}
"prettier/prettier": "warn",
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/consistent-type-imports": [
"error",
{ fixStyle: "inline-type-imports" },
],
},
}
);
Loading

0 comments on commit 85b8f33

Please sign in to comment.