Compare commits
8 Commits
install-un
...
master
Author | SHA1 | Date |
---|---|---|
![]() |
79adc5fffb | 2 months ago |
![]() |
c29b3fbc84 | 2 months ago |
![]() |
0c1f85996c | 2 months ago |
![]() |
be6f48659f | 2 months ago |
![]() |
8b8c92f1d6 | 2 months ago |
![]() |
e292cebb1d | 2 months ago |
![]() |
cb523bbe35 | 2 months ago |
![]() |
a42b6a63bb | 2 months ago |
@ -0,0 +1,2 @@
|
||||
.env*
|
||||
.dev.vars*
|
@ -0,0 +1,29 @@
|
||||
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
||||
import { personalAssistantAgent } from "./agents/personalAssistant.js";
|
||||
import { chefAgent } from "./agents/chef.js";
|
||||
import { Agent } from "./types.js";
|
||||
|
||||
// This declaration is primarily for providing type hints in your code
|
||||
interface Env {
|
||||
OPENROUTER_API_KEY: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
const env: Env;
|
||||
}
|
||||
|
||||
// Create OpenRouter instance
|
||||
export const openrouter = createOpenRouter({
|
||||
apiKey: import.meta.env.VITE_OPENROUTER_API_KEY || env.OPENROUTER_API_KEY,
|
||||
});
|
||||
|
||||
// Define the agents by ID
|
||||
export const agentsById: Record<string, Agent> = {
|
||||
"personal-assistant": personalAssistantAgent,
|
||||
chef: chefAgent,
|
||||
};
|
||||
|
||||
// Helper function to get an agent by ID
|
||||
export function getAgentById(agentId: string): Agent | undefined {
|
||||
return agentsById[agentId];
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
import {
|
||||
streamText,
|
||||
generateText,
|
||||
generateObject,
|
||||
type Message,
|
||||
jsonSchema,
|
||||
} from "ai";
|
||||
import { getAgentById, openrouter } from "./agentRegistry.js";
|
||||
|
||||
// Define the tools with explicit type
|
||||
export interface DelegateParams {
|
||||
agentId?: string;
|
||||
prompt?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface EchoParams {
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface ToolFunctions {
|
||||
[key: string]: (params: any) => Promise<string>;
|
||||
}
|
||||
|
||||
const tools: ToolFunctions = {
|
||||
delegate: async function ({
|
||||
agentId,
|
||||
prompt,
|
||||
}: DelegateParams): Promise<string> {
|
||||
// Validate required parameters
|
||||
if (!agentId || !prompt) {
|
||||
return "Error: Missing required parameters. Both 'agentId' and 'prompt' are required.";
|
||||
}
|
||||
|
||||
// Find the target agent
|
||||
const agent = getAgentById(agentId);
|
||||
if (!agent) {
|
||||
return `Error: No such agent: ${agentId}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const conversation: { messages: Array<Omit<Message, "id">> } = {
|
||||
messages: [
|
||||
{ role: "system", content: agent.systemMessage },
|
||||
{ role: "user", content: prompt },
|
||||
],
|
||||
};
|
||||
let isConversationDone = false;
|
||||
while (!isConversationDone) {
|
||||
// Generate a response from the agent using the prompt
|
||||
const result = await generateText({
|
||||
model: openrouter(agent.modelName),
|
||||
messages: conversation.messages,
|
||||
tools: agent.tools,
|
||||
});
|
||||
conversation.messages.push({ role: "assistant", content: result.text });
|
||||
const sentimentIsConversationDone = await generateObject<{
|
||||
isConversationDone: boolean;
|
||||
}>({
|
||||
model: openrouter("mistralai/mistral-nemo"),
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"You are a tool to determine whether a conversation is done or should continue with another reply.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: conversation.messages
|
||||
.map((message) => `${message.role}: ${message.content}`)
|
||||
.join("\n"),
|
||||
},
|
||||
],
|
||||
schema: jsonSchema({
|
||||
type: "object",
|
||||
properties: {
|
||||
isConversationDone: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Whether the conversation is done or should continue and requires a reply.",
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
isConversationDone =
|
||||
sentimentIsConversationDone.object.isConversationDone;
|
||||
}
|
||||
|
||||
// const summaryText = await generateSummary();
|
||||
// // Return the agent's response
|
||||
// return summaryText;
|
||||
return conversation.messages
|
||||
.map((message) => `${message.role}: ${message.content}`)
|
||||
.join("\n");
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Unknown error";
|
||||
console.error("Error delegating to agent:", error);
|
||||
return `Error delegating to agent ${agentId}: ${errorMessage}`;
|
||||
}
|
||||
},
|
||||
echo: async function ({ message }: EchoParams): Promise<string> {
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export default tools;
|
@ -1,3 +1,115 @@
|
||||
import { formatDataStreamPart, Message } from "ai";
|
||||
import tools from "./tools.js";
|
||||
import { DelegateParams } from "./tools.js";
|
||||
|
||||
export function singleSpace(str: string) {
|
||||
return str.replace(/\s+/g, " ");
|
||||
}
|
||||
|
||||
export async function processPendingToolCalls(
|
||||
messages: Message[],
|
||||
dataStreamWriter: any
|
||||
) {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
if (!lastMessage) {
|
||||
return;
|
||||
}
|
||||
if (!lastMessage.parts) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Processing pending tool calls in message:", lastMessage.id);
|
||||
|
||||
/** Execute all the pending tool calls: */
|
||||
lastMessage.parts = await Promise.all(
|
||||
lastMessage.parts?.map(async (part: any) => {
|
||||
// Check if this part has a tool invocation
|
||||
if (part.type === "tool-invocation" && part.toolInvocation) {
|
||||
const toolInvocation = part.toolInvocation as any;
|
||||
console.log("Found tool invocation:", toolInvocation);
|
||||
|
||||
if (toolInvocation.state === "result") {
|
||||
// Check if user approved the tool call
|
||||
if (toolInvocation.result === "yes") {
|
||||
try {
|
||||
// Get the tool function
|
||||
const toolName = toolInvocation.toolName || toolInvocation.name;
|
||||
console.log(`Executing tool: ${toolName}`);
|
||||
|
||||
const toolFunction = tools[toolName as keyof typeof tools];
|
||||
|
||||
if (toolFunction) {
|
||||
// Extract parameters from the tool invocation
|
||||
let parameters = {};
|
||||
try {
|
||||
if (toolInvocation.parameters) {
|
||||
parameters = JSON.parse(toolInvocation.parameters);
|
||||
} else if (toolInvocation.args) {
|
||||
parameters = toolInvocation.args;
|
||||
}
|
||||
console.log(`Tool parameters:`, parameters);
|
||||
} catch (e) {
|
||||
console.error("Error parsing tool parameters:", e);
|
||||
}
|
||||
|
||||
// Call the tool function with the parameters
|
||||
console.log(
|
||||
`Calling tool function with parameters:`,
|
||||
parameters
|
||||
);
|
||||
const result = await toolFunction(parameters as DelegateParams);
|
||||
console.log(`Tool result:`, result);
|
||||
|
||||
// forward updated tool result to the client:
|
||||
dataStreamWriter.write(
|
||||
formatDataStreamPart("tool_result", {
|
||||
toolCallId: toolInvocation.toolCallId,
|
||||
result,
|
||||
})
|
||||
);
|
||||
|
||||
// update the message part:
|
||||
return {
|
||||
...part,
|
||||
toolInvocation: { ...toolInvocation, result },
|
||||
};
|
||||
|
||||
// // Set the result
|
||||
// toolInvocation.result = result;
|
||||
|
||||
// // Add a new message with the tool result
|
||||
// if (result) {
|
||||
// messages.push({
|
||||
// id: `tool-result-${Date.now()}`,
|
||||
// role: "assistant",
|
||||
// content: `Tool Result: ${result}`,
|
||||
// parts: [
|
||||
// {
|
||||
// type: "text",
|
||||
// text: `Tool Result: ${result}`,
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// }
|
||||
} else {
|
||||
const errorMsg = `Error: Tool '${toolName}' not found`;
|
||||
console.error(errorMsg);
|
||||
toolInvocation.result = errorMsg;
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Unknown error";
|
||||
console.error("Error executing tool:", error);
|
||||
toolInvocation.result = `Error executing tool: ${errorMessage}`;
|
||||
}
|
||||
} else if (toolInvocation.result === "no") {
|
||||
toolInvocation.result = "Error: User denied tool call.";
|
||||
}
|
||||
}
|
||||
}
|
||||
return part;
|
||||
}) ?? []
|
||||
);
|
||||
|
||||
console.log("Finished processing tool calls. Updated messages:", messages);
|
||||
}
|
||||
|
@ -1,77 +1,66 @@
|
||||
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
||||
import { streamText } from "ai";
|
||||
import { streamText, Message, createDataStream } 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;
|
||||
}
|
||||
import { processPendingToolCalls } from "./util.js";
|
||||
import { agentsById, openrouter } from "./agentRegistry.js";
|
||||
|
||||
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: {},
|
||||
});
|
||||
app.post("/api/chat/:agent_id", async (c) => {
|
||||
const input: { messages: Message[] } = await c.req.json();
|
||||
const agentId = c.req.param("agent_id");
|
||||
const agent = agentsById[agentId];
|
||||
if (!agent) {
|
||||
c.status(404);
|
||||
return c.json({ error: `No such agent: ${agentId}` });
|
||||
}
|
||||
|
||||
// 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");
|
||||
const dataStream = createDataStream({
|
||||
execute: async (dataStreamWriter) => {
|
||||
// dataStreamWriter.writeData('initialized call');
|
||||
|
||||
return stream(c, (stream) => stream.pipe(result.toDataStream()));
|
||||
});
|
||||
// Process any pending tool calls in the messages
|
||||
// This modifies the messages array in place
|
||||
await processPendingToolCalls(input.messages, dataStreamWriter);
|
||||
|
||||
const agentsByName: Record<string, Agent> = {
|
||||
assistant: personalAssistantAgent,
|
||||
chef: chefAgent,
|
||||
};
|
||||
const result = streamText({
|
||||
model: openrouter(agent.modelName),
|
||||
maxSteps: 5,
|
||||
messages: [
|
||||
{ role: "system", content: agent.systemMessage },
|
||||
...Object.values(agentsById).map((agent) => ({
|
||||
role: "system" as const,
|
||||
content: `Agent ${JSON.stringify({
|
||||
id: agent.id,
|
||||
name: agent.name,
|
||||
description: agent.description,
|
||||
skills: agent.skills,
|
||||
})}`,
|
||||
})),
|
||||
...input.messages,
|
||||
],
|
||||
tools: agent.tools,
|
||||
onError: (error) => {
|
||||
console.error("Error in streamText:", error);
|
||||
},
|
||||
});
|
||||
|
||||
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,
|
||||
result.mergeIntoDataStream(dataStreamWriter);
|
||||
},
|
||||
onError: (error) => {
|
||||
// Error messages are masked by default for security reasons.
|
||||
// If you want to expose the error message to the client, you can do so here:
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
},
|
||||
});
|
||||
|
||||
// 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()));
|
||||
return stream(c, (stream) =>
|
||||
stream.pipe(dataStream.pipeThrough(new TextEncoderStream()))
|
||||
);
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
Loading…
Reference in New Issue