Skip to content

Commit cd50a33

Browse files
feat: implement interpreter tool (#94)
--------- Co-authored-by: Marcus Schiesser <[email protected]>
1 parent ed11485 commit cd50a33

File tree

19 files changed

+370
-55
lines changed

19 files changed

+370
-55
lines changed

.changeset/gentle-knives-unite.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-llama": patch
3+
---
4+
5+
Add interpreter tool for TS using e2b.dev

.github/workflows/e2e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
- uses: actions/checkout@v4
2727

2828
- name: Set up python ${{ matrix.python-version }}
29-
uses: actions/setup-python@v4
29+
uses: actions/setup-python@v5
3030
with:
3131
python-version: ${{ matrix.python-version }}
3232

helpers/env-variables.ts

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import fs from "fs/promises";
22
import path from "path";
3+
import { TOOL_SYSTEM_PROMPT_ENV_VAR, Tool } from "./tools";
34
import {
45
ModelConfig,
56
TemplateDataSource,
67
TemplateFramework,
78
TemplateVectorDB,
89
} from "./types";
910

10-
type EnvVar = {
11+
export type EnvVar = {
1112
name?: string;
1213
description?: string;
1314
value?: string;
@@ -232,24 +233,39 @@ const getModelEnvs = (modelConfig: ModelConfig): EnvVar[] => {
232233
};
233234

234235
const getFrameworkEnvs = (
235-
framework?: TemplateFramework,
236+
framework: TemplateFramework,
236237
port?: number,
237238
): EnvVar[] => {
238-
if (framework !== "fastapi") {
239-
return [];
240-
}
241-
return [
242-
{
243-
name: "APP_HOST",
244-
description: "The address to start the backend app.",
245-
value: "0.0.0.0",
246-
},
239+
const sPort = port?.toString() || "8000";
240+
const result: EnvVar[] = [
247241
{
248-
name: "APP_PORT",
249-
description: "The port to start the backend app.",
250-
value: port?.toString() || "8000",
242+
name: "FILESERVER_URL_PREFIX",
243+
description:
244+
"FILESERVER_URL_PREFIX is the URL prefix of the server storing the images generated by the interpreter.",
245+
value:
246+
framework === "nextjs"
247+
? // FIXME: if we are using nextjs, port should be 3000
248+
"http://localhost:3000/api/files"
249+
: `http://localhost:${sPort}/api/files`,
251250
},
252251
];
252+
if (framework === "fastapi") {
253+
result.push(
254+
...[
255+
{
256+
name: "APP_HOST",
257+
description: "The address to start the backend app.",
258+
value: "0.0.0.0",
259+
},
260+
{
261+
name: "APP_PORT",
262+
description: "The port to start the backend app.",
263+
value: sPort,
264+
},
265+
],
266+
);
267+
}
268+
return result;
253269
};
254270

255271
const getEngineEnvs = (): EnvVar[] => {
@@ -260,24 +276,62 @@ const getEngineEnvs = (): EnvVar[] => {
260276
"The number of similar embeddings to return when retrieving documents.",
261277
value: "3",
262278
},
263-
{
264-
name: "SYSTEM_PROMPT",
265-
description: `Custom system prompt.
266-
Example:
267-
SYSTEM_PROMPT="You are a helpful assistant who helps users with their questions."`,
268-
},
269279
];
270280
};
271281

282+
const getToolEnvs = (tools?: Tool[]): EnvVar[] => {
283+
if (!tools?.length) return [];
284+
const toolEnvs: EnvVar[] = [];
285+
tools.forEach((tool) => {
286+
if (tool.envVars?.length) {
287+
toolEnvs.push(
288+
// Don't include the system prompt env var here
289+
// It should be handled separately by merging with the default system prompt
290+
...tool.envVars.filter(
291+
(env) => env.name !== TOOL_SYSTEM_PROMPT_ENV_VAR,
292+
),
293+
);
294+
}
295+
});
296+
return toolEnvs;
297+
};
298+
299+
const getSystemPromptEnv = (tools?: Tool[]): EnvVar => {
300+
const defaultSystemPrompt =
301+
"You are a helpful assistant who helps users with their questions.";
302+
303+
// build tool system prompt by merging all tool system prompts
304+
let toolSystemPrompt = "";
305+
tools?.forEach((tool) => {
306+
const toolSystemPromptEnv = tool.envVars?.find(
307+
(env) => env.name === TOOL_SYSTEM_PROMPT_ENV_VAR,
308+
);
309+
if (toolSystemPromptEnv) {
310+
toolSystemPrompt += toolSystemPromptEnv.value + "\n";
311+
}
312+
});
313+
314+
const systemPrompt = toolSystemPrompt
315+
? `\"${toolSystemPrompt}\"`
316+
: defaultSystemPrompt;
317+
318+
return {
319+
name: "SYSTEM_PROMPT",
320+
description: "The system prompt for the AI model.",
321+
value: systemPrompt,
322+
};
323+
};
324+
272325
export const createBackendEnvFile = async (
273326
root: string,
274327
opts: {
275328
llamaCloudKey?: string;
276329
vectorDb?: TemplateVectorDB;
277330
modelConfig: ModelConfig;
278-
framework?: TemplateFramework;
331+
framework: TemplateFramework;
279332
dataSources?: TemplateDataSource[];
280333
port?: number;
334+
tools?: Tool[];
281335
},
282336
) => {
283337
// Init env values
@@ -295,6 +349,8 @@ export const createBackendEnvFile = async (
295349
// Add vector database environment variables
296350
...getVectorDBEnvs(opts.vectorDb, opts.framework),
297351
...getFrameworkEnvs(opts.framework, opts.port),
352+
...getToolEnvs(opts.tools),
353+
getSystemPromptEnv(opts.tools),
298354
];
299355
// Render and write env file
300356
const content = renderEnvVar(envVars);

helpers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ export const installTemplate = async (
148148
framework: props.framework,
149149
dataSources: props.dataSources,
150150
port: props.externalPort,
151+
tools: props.tools,
151152
});
152153

153154
if (props.dataSources.length > 0) {

helpers/tools.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import fs from "fs/promises";
22
import path from "path";
33
import { red } from "picocolors";
44
import yaml from "yaml";
5+
import { EnvVar } from "./env-variables";
56
import { makeDir } from "./make-dir";
67
import { TemplateFramework } from "./types";
78

9+
export const TOOL_SYSTEM_PROMPT_ENV_VAR = "TOOL_SYSTEM_PROMPT";
10+
811
export enum ToolType {
912
LLAMAHUB = "llamahub",
1013
LOCAL = "local",
@@ -17,6 +20,7 @@ export type Tool = {
1720
dependencies?: ToolDependencies[];
1821
supportedFrameworks?: Array<TemplateFramework>;
1922
type: ToolType;
23+
envVars?: EnvVar[];
2024
};
2125

2226
export type ToolDependencies = {
@@ -42,6 +46,13 @@ export const supportedTools: Tool[] = [
4246
],
4347
supportedFrameworks: ["fastapi"],
4448
type: ToolType.LLAMAHUB,
49+
envVars: [
50+
{
51+
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
52+
description: "System prompt for google search tool.",
53+
value: `You are a Google search agent. You help users to get information from Google search.`,
54+
},
55+
],
4556
},
4657
{
4758
display: "Wikipedia",
@@ -54,13 +65,52 @@ export const supportedTools: Tool[] = [
5465
],
5566
supportedFrameworks: ["fastapi", "express", "nextjs"],
5667
type: ToolType.LLAMAHUB,
68+
envVars: [
69+
{
70+
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
71+
description: "System prompt for wiki tool.",
72+
value: `You are a Wikipedia agent. You help users to get information from Wikipedia.`,
73+
},
74+
],
5775
},
5876
{
5977
display: "Weather",
6078
name: "weather",
6179
dependencies: [],
6280
supportedFrameworks: ["fastapi", "express", "nextjs"],
6381
type: ToolType.LOCAL,
82+
envVars: [
83+
{
84+
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
85+
description: "System prompt for weather tool.",
86+
value: `You are a weather forecast agent. You help users to get the weather forecast for a given location.`,
87+
},
88+
],
89+
},
90+
{
91+
display: "Code Interpreter",
92+
name: "interpreter",
93+
dependencies: [],
94+
supportedFrameworks: ["express", "nextjs"],
95+
type: ToolType.LOCAL,
96+
envVars: [
97+
{
98+
name: "E2B_API_KEY",
99+
description:
100+
"E2B_API_KEY key is required to run code interpreter tool. Get it here: https://e2b.dev/docs/getting-started/api-key",
101+
},
102+
{
103+
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
104+
description: "System prompt for code interpreter tool.",
105+
value: `You are a Python interpreter.
106+
- You are given tasks to complete and you run python code to solve them.
107+
- The python code runs in a Jupyter notebook. Every time you call \`interpreter\` tool, the python code is executed in a separate cell. It's okay to make multiple calls to \`interpreter\`.
108+
- Display visualizations using matplotlib or any other visualization library directly in the notebook. Shouldn't save the visualizations to a file, just return the base64 encoded data.
109+
- You can install any pip package (if it exists) if you need to but the usual packages for data analysis are already preinstalled.
110+
- You can run any python code you want in a secure environment.
111+
- Use absolute url from result to display images or any other media.`,
112+
},
113+
],
64114
},
65115
];
66116

templates/components/engines/typescript/agent/chat.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { BaseToolWithCall, OpenAIAgent, QueryEngineTool } from "llamaindex";
2-
import { ToolsFactory } from "llamaindex/tools/ToolsFactory";
32
import fs from "node:fs/promises";
43
import path from "node:path";
54
import { getDataSource } from "./index";
65
import { STORAGE_CACHE_DIR } from "./shared";
7-
import { createLocalTools } from "./tools";
6+
import { createTools } from "./tools";
87

98
export async function createChatEngine() {
109
const tools: BaseToolWithCall[] = [];
@@ -24,20 +23,17 @@ export async function createChatEngine() {
2423
);
2524
}
2625

26+
const configFile = path.join("config", "tools.json");
27+
let toolConfig: any;
2728
try {
2829
// add tools from config file if it exists
29-
const config = JSON.parse(
30-
await fs.readFile(path.join("config", "tools.json"), "utf8"),
31-
);
32-
33-
// add local tools from the 'tools' folder (if configured)
34-
const localTools = createLocalTools(config.local);
35-
tools.push(...localTools);
36-
37-
// add tools from LlamaIndexTS (if configured)
38-
const llamaTools = await ToolsFactory.createTools(config.llamahub);
39-
tools.push(...llamaTools);
40-
} catch {}
30+
toolConfig = JSON.parse(await fs.readFile(configFile, "utf8"));
31+
} catch (e) {
32+
console.info(`Could not read ${configFile} file. Using no tools.`);
33+
}
34+
if (toolConfig) {
35+
tools.push(...(await createTools(toolConfig)));
36+
}
4137

4238
return new OpenAIAgent({
4339
tools,

templates/components/engines/typescript/agent/tools/index.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
11
import { BaseToolWithCall } from "llamaindex";
2+
import { ToolsFactory } from "llamaindex/tools/ToolsFactory";
3+
import { InterpreterTool, InterpreterToolParams } from "./interpreter";
24
import { WeatherTool, WeatherToolParams } from "./weather";
35

46
type ToolCreator = (config: unknown) => BaseToolWithCall;
57

8+
export async function createTools(toolConfig: {
9+
local: Record<string, unknown>;
10+
llamahub: any;
11+
}): Promise<BaseToolWithCall[]> {
12+
// add local tools from the 'tools' folder (if configured)
13+
const tools = createLocalTools(toolConfig.local);
14+
// add tools from LlamaIndexTS (if configured)
15+
tools.push(...(await ToolsFactory.createTools(toolConfig.llamahub)));
16+
return tools;
17+
}
18+
619
const toolFactory: Record<string, ToolCreator> = {
720
weather: (config: unknown) => {
821
return new WeatherTool(config as WeatherToolParams);
922
},
23+
interpreter: (config: unknown) => {
24+
return new InterpreterTool(config as InterpreterToolParams);
25+
},
1026
};
1127

12-
export function createLocalTools(
28+
function createLocalTools(
1329
localConfig: Record<string, unknown>,
1430
): BaseToolWithCall[] {
1531
const tools: BaseToolWithCall[] = [];

0 commit comments

Comments
 (0)