initial commit: frontend and backend; /, /chat, /chats/:agent_name routes
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import { jsonSchema, tool } from "ai";
|
||||
import { Agent } from "../types.js";
|
||||
import { singleSpace } from "../util.js";
|
||||
|
||||
export const personalAssistantAgent: Agent = {
|
||||
id: "personal-assistant",
|
||||
name: "Personal Assistant",
|
||||
// modelName: "mistral/ministral-8b",
|
||||
modelName: "google/gemini-2.5-flash-preview",
|
||||
systemMessage:
|
||||
singleSpace(`You are a personal assistant Agent who is helpful, friendly, and
|
||||
responsible. You are my liason to other Agents who have specialized
|
||||
abilities and skills. I will inform you of the existence of these Agents by
|
||||
giving you each one's "Agent Card" in a structured format. You will delegate
|
||||
tasks to the Agent(s) that you deem most suitable, based on their skills and abilities. You
|
||||
will provide those Agents with the necessary information and context
|
||||
to complete the task effectively and efficiently; you will not include
|
||||
irrelevant details, and you will include relevant details and context.
|
||||
You may delegate to multiple Agents if the task or request can be split
|
||||
into subtasks each requiring distinct skills/abilities. If multiple Agents
|
||||
are equally-qualified to handle the task, you will ask me which one to delegate
|
||||
to, because you are responsible and do not want to choose anything but the most
|
||||
relevant Agent for a given task. Similarly, if you think there is no
|
||||
sufficiently-capable Agent to handle a given task, you will tell me so and exit
|
||||
the conversation so I can go create one.`),
|
||||
description:
|
||||
"A personal assistant who is helpful, friendly, and knowledgeable.",
|
||||
skills: [
|
||||
{
|
||||
id: "help",
|
||||
name: "Help",
|
||||
description: "Provides helpful information.",
|
||||
tags: ["help", "information"],
|
||||
examples: [
|
||||
"What is the weather like today?",
|
||||
"How do I cook a lasagna?",
|
||||
"Can you tell me about the history of the United States?",
|
||||
],
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
delegate: tool({
|
||||
description:
|
||||
"Delegate the task to another Agent that you deem most suitable.",
|
||||
parameters: jsonSchema<{ agentId: string; prompt: string }>({
|
||||
type: "object",
|
||||
properties: {
|
||||
agentId: {
|
||||
type: "string",
|
||||
description: "The ID of the Agent to delegate the task to.",
|
||||
},
|
||||
prompt: {
|
||||
type: "string",
|
||||
description:
|
||||
"The prompt to use for the delegated task which includes all necessary information and context for the Agent to be able to complete the task.",
|
||||
},
|
||||
},
|
||||
}),
|
||||
// execute: async ({
|
||||
// agentId,
|
||||
// prompt,
|
||||
// }: {
|
||||
// agentId: string;
|
||||
// prompt: string;
|
||||
// }) => {
|
||||
// const agent = agents.find((agent) => agent.id === agentId);
|
||||
// if (!agent) {
|
||||
// throw new Error(`No such agent: ${agentId}`);
|
||||
// }
|
||||
// const response = conversation.send({
|
||||
// role: "user",
|
||||
// content: prompt,
|
||||
// });
|
||||
// await response.consumeStream();
|
||||
// for (const message of (await response.response).messages) {
|
||||
// conversation.messages.push(message);
|
||||
// }
|
||||
// },
|
||||
}),
|
||||
|
||||
say: tool({
|
||||
description: "Say something.",
|
||||
parameters: jsonSchema<{ message: string }>({
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
description: "The message to say.",
|
||||
},
|
||||
},
|
||||
}),
|
||||
execute: async ({ message }: { message: string }) => {
|
||||
console.log(message);
|
||||
},
|
||||
}),
|
||||
exit: tool({
|
||||
description: "Exits the conversation.",
|
||||
parameters: jsonSchema<{ exitCode: number }>({
|
||||
type: "object",
|
||||
properties: {
|
||||
exitCode: {
|
||||
type: "number",
|
||||
description: "The exit code to use.",
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
get_random_number: tool({
|
||||
description: "Get a random number between `min` and `max`.",
|
||||
parameters: jsonSchema<{ min: number; max: number }>({
|
||||
type: "object",
|
||||
properties: {
|
||||
min: {
|
||||
type: "number",
|
||||
description: "The minimum value.",
|
||||
default: 0,
|
||||
},
|
||||
max: {
|
||||
type: "number",
|
||||
description: "The maximum value.",
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
execute: async ({ min, max }: { min: number; max: number }) => {
|
||||
return Math.random() * (max - min) + min;
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Agent } from "../types.js";
|
||||
|
||||
export const chefAgent: Agent = {
|
||||
id: "chef",
|
||||
name: "Chef",
|
||||
modelName: "mistral/ministral-8b",
|
||||
systemMessage:
|
||||
"You are a master chef who is famous for his creative and original recipes, but also knows many standard, tried-and-true recipes.",
|
||||
description:
|
||||
"A master chef who is famous for his creative and original recipes, but also knows many standard, tried-and-true recipes.",
|
||||
skills: [
|
||||
{
|
||||
id: "invent_recipe",
|
||||
name: "Invent Recipe",
|
||||
description:
|
||||
"Creates an original recipe, as opposed to a conventional one.",
|
||||
tags: ["cooking", "recipe", "invention"],
|
||||
examples: [
|
||||
"Create a vegetarian lasagna recipe for 4 people.",
|
||||
"Invent a recipe for a chicken parmesan.",
|
||||
"Create a recipe for a vegan lasagna.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "recall_recipe",
|
||||
name: "Recall Recipe",
|
||||
description:
|
||||
"Recalls a conventional recipe, as opposed to an original one.",
|
||||
tags: ["cooking", "recipe", "recall"],
|
||||
examples: [
|
||||
"Create a vegetarian lasagna recipe for 4 people.",
|
||||
"Invent a recipe for a chicken parmesan.",
|
||||
"Create a recipe for a vegan lasagna.",
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Tool } from "ai";
|
||||
|
||||
/** An Agent's Skill does not imply any specific tool; tool-use is strictly
|
||||
* internal to the Agent and considered an implementation detail. From the
|
||||
* perspective of other Agents, a Skill is simply an assertion that a
|
||||
* sufficiently-relevant task delegated to this agent will more likely be
|
||||
* accomplished successfully than with another, less-relevant Agent/Skill.
|
||||
*
|
||||
* Note that the granularity of task delegation is to an Agent, not to a
|
||||
* specific skill. We use this `Skill` interface as a compact, standardized way
|
||||
* of expressing task relevancy to other Agents; instead of relying solely on
|
||||
* the natural-language `description`.
|
||||
* */
|
||||
export interface Skill {
|
||||
/** Machine-friendly name. */
|
||||
id: string;
|
||||
/** Human-freindly name. */
|
||||
name: string;
|
||||
/** The description of the skill. */
|
||||
description: string;
|
||||
tags?: string[];
|
||||
examples?: string[];
|
||||
}
|
||||
|
||||
export interface Agent {
|
||||
/** Machine-friendly name. */
|
||||
id: string;
|
||||
/** Human-freindly name. */
|
||||
name: string;
|
||||
modelName: string;
|
||||
/** The system prompt. Not exposed to other models. Therefore, it's safe to
|
||||
* include instructions like "DO" and "DO NOT", etc. */
|
||||
systemMessage: string;
|
||||
/** Exposed to other Agents, to help tham decide whether to delegate a given
|
||||
* task to this Agent. */
|
||||
description: string;
|
||||
/** See JSDoc on the `Skill` interface. */
|
||||
skills?: Skill[];
|
||||
tools?: Record<string, Tool>;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function singleSpace(str: string) {
|
||||
return str.replace(/\s+/g, " ");
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
||||
import { streamText } from "ai";
|
||||
import { Hono } from "hono";
|
||||
import { stream } from "hono/streaming";
|
||||
import { personalAssistantAgent } from "./agents/assistant.js";
|
||||
import { chefAgent } from "./agents/chef.js";
|
||||
import { Agent } from "./types.js";
|
||||
|
||||
// This declaration is primarily for providing type hints in your code
|
||||
// and it doesn't directly define the *values* of the environment variables.
|
||||
|
||||
interface Env {
|
||||
OPENROUTER_API_KEY: string;
|
||||
// Add other environment variables here
|
||||
}
|
||||
|
||||
declare global {
|
||||
const env: Env;
|
||||
}
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
const openrouter = createOpenRouter({
|
||||
apiKey: import.meta.env.VITE_OPENROUTER_API_KEY || env.OPENROUTER_API_KEY,
|
||||
});
|
||||
const systemMessage = {
|
||||
role: "system",
|
||||
content:
|
||||
"You are a wise old man named Dorf that answers questions succintly.",
|
||||
};
|
||||
|
||||
app.post("/api/chat", async (c) => {
|
||||
const input = await c.req.json();
|
||||
console.log(input);
|
||||
const result = streamText({
|
||||
model: openrouter("mistral/ministral-8b"),
|
||||
messages: [systemMessage, ...input.messages],
|
||||
tools: {},
|
||||
});
|
||||
|
||||
// Mark the response as a v1 data stream:
|
||||
c.header("X-Vercel-AI-Data-Stream", "v1");
|
||||
c.header("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
return stream(c, (stream) => stream.pipe(result.toDataStream()));
|
||||
});
|
||||
|
||||
const agentsByName: Record<string, Agent> = {
|
||||
assistant: personalAssistantAgent,
|
||||
chef: chefAgent,
|
||||
};
|
||||
|
||||
app.post("/api/chat/:agent_name", async (c) => {
|
||||
const input = await c.req.json();
|
||||
const agentName = c.req.param("agent_name");
|
||||
const agent = agentsByName[agentName];
|
||||
if (!agent) {
|
||||
return c.json({ error: `No such agent: ${agentName}` });
|
||||
}
|
||||
console.log(input);
|
||||
const result = streamText({
|
||||
model: openrouter(agent.modelName),
|
||||
messages: [
|
||||
{ role: "system", content: agent.systemMessage },
|
||||
...input.messages,
|
||||
],
|
||||
tools: agent.tools,
|
||||
});
|
||||
|
||||
// Mark the response as a v1 data stream:
|
||||
c.header("X-Vercel-AI-Data-Stream", "v1");
|
||||
c.header("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
return stream(c, (stream) => stream.pipe(result.toDataStream()));
|
||||
});
|
||||
|
||||
export default app;
|
||||
Reference in New Issue
Block a user