Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Agent Tool example #97

Merged
merged 3 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions agent-tool/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

# For inference
RESTACK_API_KEY=

# For Restack Cloud deployment
RESTACK_ENGINE_ID=
RESTACK_ENGINE_ADDRESS=
RESTACK_ENGINE_API_KEY=
RESTACK_ENGINE_API_ADDRESS=

Binary file added agent-tool/chat_post.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added agent-tool/chat_put.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added agent-tool/chat_run.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions agent-tool/eventAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { client } from "./src/client";

export type EventInput = {
agentId: string;
runId: string;
};

async function eventAgent(input: EventInput) {
try {
await client.sendAgentEvent({
event: {
name: "message",
input: { content: "Sales on boots?" },
},
agent: {
agentId: input.agentId,
runId: input.runId,
},
});

await client.sendAgentEvent({
event: {
name: "end",
},
agent: {
agentId: input.agentId,
runId: input.runId,
},
});

process.exit(0); // Exit the process successfully
} catch (error) {
console.error("Error sending event to agent:", error);
process.exit(1); // Exit the process with an error code
}
}

eventAgent({
agentId: "your-agent-id",
runId: "your-run-id",
});
26 changes: 26 additions & 0 deletions agent-tool/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "agent-tool",
"version": "0.0.1",
"description": "Restack Agent with Tool Calling example",
"scripts": {
"dev": "open-cli http://localhost:5233 && tsx watch --include src src/services.ts",
"build": "tsc --build",
"clean": "rm -rf node_modules",
"schedule": "tsx scheduleAgent.ts",
"event": "tsx eventAgent.ts"
},
"dependencies": {
"@restackio/ai": "^0.0.104",
"@temporalio/workflow": "1.11.6",
"openai": "^4.80.1",
"zod": "^3.24.1"
},
"devDependencies": {
"@types/node": "20.16.9",
"dotenv-cli": "^7.4.2",
"open-cli": "^8.0.0",
"prettier": "3.3.3",
"tsx": "4.19.2",
"typescript": "^5.7.2"
}
}
104 changes: 104 additions & 0 deletions agent-tool/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Restack Agent with Tool Calling

A sample repository with an agent using tool calling.

## Requirements

- **Node 20+**

## Start Restack

To start Restack, use the following Docker command:

```bash
docker run -d --pull always --name restack -p 5233:5233 -p 6233:6233 -p 7233:7233 ghcr.io/restackio/restack:main
```

## Install dependencies and start services

```bash
npm install
npm run dev
```

This will start a Node.js app with Restack Services.
Your code will be running and syncing with Restack to execute agents.

## Run agents

### from UI

You can run agents from the UI by clicking the "Run" button.

![Run agents from UI](./chat_post.png)

### from API

You can run agents from the API by using the generated endpoint:

`POST http://localhost:6233/api/agents/agentChatTool`

### from any client

You can run agents with any client connected to Restack, for example:

```bash
npm run schedule
```

executes `scheduleAgent.ts` which will connect to Restack and execute the `agentChatTool` agent.

## Send events to the Agent

### from UI

You can send events like message or end from the UI.

![Send events from UI](./chat_put.png)

And see the events in the run:

![See events in UI](./chat_run.png)

### from API

You can send events to the agent by using the following endpoint:

`PUT http://localhost:6233/api/agents/agentChatTool/:agentId/:runId`

with the payload:

```json
{
"eventName": "message",
"eventInput": { "content": "Sales on boots?" }
}
```

to send messages to the agent.

or

```json
{
"eventName": "end"
}
```

to end the conversation with the agent.

### from any client

You can send event to the agent with any client connected to Restack, for example:

Modify agentId and runId in eventAgent.ts and then run:

```bash
npm run event
```

It will connect to Restack and send 2 events to the agent, one to generate another agent and another one to end the conversation.

## Deploy on Restack Cloud

To deploy the application on Restack, you can create an account at [https://console.restack.io](https://console.restack.io)
29 changes: 29 additions & 0 deletions agent-tool/scheduleAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { client } from "./src/client";
import { agentChatTool } from "./src/agents/agent";
export type InputSchedule = {
name: string;
};

async function scheduleAgent(input: InputSchedule) {
try {
const agentId = `${Date.now()}-${agentChatTool.name}`;
const runId = await client.scheduleAgent({
agentName: agentChatTool.name,
agentId,
input,
});

const result = await client.getAgentResult({ agentId, runId });

console.log("Agent result:", result);

process.exit(0); // Exit the process successfully
} catch (error) {
console.error("Error scheduling agent:", error);
process.exit(1); // Exit the process with an error code
}
}

scheduleAgent({
name: "test",
});
76 changes: 76 additions & 0 deletions agent-tool/src/agents/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
defineEvent,
onEvent,
condition,
log,
step,
} from "@restackio/ai/agent";
import * as functions from "../functions";

export type EndEvent = {
end: boolean;
};

export const messageEvent = defineEvent<functions.Message[]>("message");
export const endEvent = defineEvent("end");

type AgentChatOutput = {
messages: functions.Message[];
};

export async function agentChatTool(): Promise<AgentChatOutput> {
let endReceived = false;
let messages: functions.Message[] = [];

const tools = await step<typeof functions.getTools>({}).getTools();

onEvent(messageEvent, async ({ content }: functions.Message) => {
messages.push({ role: "user", content });
const result = await step<typeof functions.llmChat>({}).llmChat({
messages,
tools,
});

messages.push(result);

if (result.tool_calls) {
for (const toolCall of result.tool_calls) {
switch (toolCall.function.name) {
case "lookupSales":
const toolResult = await step<typeof functions.lookupSales>(
{}
).lookupSales(JSON.parse(toolCall.function.arguments));

messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(toolResult),
});

const toolChatResult = await step<typeof functions.llmChat>(
{}
).llmChat({
messages,
tools,
});

messages.push(toolChatResult);

break;
default:
break;
}
}
}
return messages;
});

onEvent(endEvent, async () => {
endReceived = true;
});

await condition(() => endReceived);

log.info("end condition met");
return { messages };
}
1 change: 1 addition & 0 deletions agent-tool/src/agents/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./agent";
14 changes: 14 additions & 0 deletions agent-tool/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Restack from "@restackio/ai";

import "dotenv/config";

export const connectionOptions = {
engineId: process.env.RESTACK_ENGINE_ID!,
address: process.env.RESTACK_ENGINE_ADDRESS!,
apiKey: process.env.RESTACK_ENGINE_API_KEY!,
apiAddress: process.env.RESTACK_ENGINE_API_ADDRESS!,
};

export const client = new Restack(
process.env.RESTACK_ENGINE_API_KEY ? connectionOptions : undefined
);
13 changes: 13 additions & 0 deletions agent-tool/src/functions/getTools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { zodFunction } from "openai/helpers/zod";
import { lookupSales } from "./lookupSales";
import { LookupSalesInput } from "./toolTypes";

export const getTools = async () => {
const tools = [
zodFunction({
name: lookupSales.name,
parameters: LookupSalesInput,
}),
];
return tools;
};
3 changes: 3 additions & 0 deletions agent-tool/src/functions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./llmChat";
export * from "./lookupSales";
export * from "./getTools";
58 changes: 58 additions & 0 deletions agent-tool/src/functions/llmChat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { FunctionFailure, log } from "@restackio/ai/function";
import {
ChatCompletionCreateParamsNonStreaming,
ChatCompletionMessage,
ChatCompletionMessageParam,
ChatCompletionSystemMessageParam,
ChatCompletionTool,
ChatCompletionToolMessageParam,
ChatCompletionUserMessageParam,
} from "openai/resources/chat/completions";

import { openaiClient } from "../utils/client";

export type Message =
| ChatCompletionSystemMessageParam
| ChatCompletionUserMessageParam
| ChatCompletionToolMessageParam;

export type OpenAIChatInput = {
systemContent?: string;
model?: string;
messages: Message[];
tools?: ChatCompletionTool[];
};

export const llmChat = async ({
systemContent = "",
model = "gpt-4o-mini",
messages,
tools,
}: OpenAIChatInput): Promise<ChatCompletionMessage> => {
try {
const openai = openaiClient({});

const chatParams: ChatCompletionCreateParamsNonStreaming = {
messages: [
...(systemContent
? [{ role: "system" as const, content: systemContent }]
: []),
...(messages ?? []),
],
model,
tools,
};

log.debug("OpenAI chat completion params", {
chatParams,
});

const completion = await openai.chat.completions.create(chatParams);

const message = completion.choices[0].message;

return message;
} catch (error) {
throw FunctionFailure.nonRetryable(`Error OpenAI chat: ${error}`);
}
};
Loading