Compare commits

..

4 Commits

Author SHA1 Message Date
github-actions[bot] 163492f189 Release 0.3.24 (#472)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-27 09:54:29 +07:00
Huu Le a84743c576 add LlamaCloud support for reflex template (#473) 2024-12-26 15:09:16 +07:00
Thuc Pham fc5e56efa5 bump: code interpreter v1 (#469) 2024-12-26 15:06:00 +07:00
Huu Le a7a6592441 Fix the npm issue when running a fullstack Python app (#471) 2024-12-25 10:28:50 +07:00
25 changed files with 696 additions and 278 deletions
+9
View File
@@ -1,5 +1,14 @@
# create-llama
## 0.3.24
### Patch Changes
- a84743c: Change --agents paramameter to --use-case
- a84743c: Add LlamaCloud support for Reflex templates
- a7a6592: Fix the npm issue on the full-stack Python template
- fc5e56e: bump: code interpreter v1
## 0.3.23
### Patch Changes
+2 -2
View File
@@ -39,7 +39,7 @@ export async function createApp({
tools,
useLlamaParse,
observability,
agents,
useCase,
}: InstallAppArgs): Promise<void> {
const root = path.resolve(appPath);
@@ -84,7 +84,7 @@ export async function createApp({
tools,
useLlamaParse,
observability,
agents,
useCase,
};
// Install backend
+6 -6
View File
@@ -18,10 +18,10 @@ const templateUI: TemplateUI = "shadcn";
const templatePostInstallAction: TemplatePostInstallAction = "runApp";
const appType: AppType = templateFramework === "fastapi" ? "--frontend" : "";
const userMessage = "Write a blog post about physical standards for letters";
const templateAgents = ["financial_report", "blog", "form_filling"];
const templateUseCases = ["financial_report", "blog", "form_filling"];
for (const agents of templateAgents) {
test.describe(`Test multiagent template ${agents} ${templateFramework} ${dataSource} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
for (const useCase of templateUseCases) {
test.describe(`Test multiagent template ${useCase} ${templateFramework} ${dataSource} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
test.skip(
process.platform !== "linux" || process.env.DATASOURCE === "--no-files",
"The multiagent template currently only works with files. We also only run on Linux to speed up tests.",
@@ -46,7 +46,7 @@ for (const agents of templateAgents) {
postInstallAction: templatePostInstallAction,
templateUI,
appType,
agents,
useCase,
});
name = result.projectName;
appProcess = result.appProcess;
@@ -71,8 +71,8 @@ for (const agents of templateAgents) {
}) => {
test.skip(
templatePostInstallAction !== "runApp" ||
agents === "financial_report" ||
agents === "form_filling" ||
useCase === "financial_report" ||
useCase === "form_filling" ||
templateFramework === "express",
"Skip chat tests for financial report and form filling.",
);
+5 -5
View File
@@ -3,7 +3,7 @@ import { expect, test } from "@playwright/test";
import { ChildProcess } from "child_process";
import fs from "fs";
import path from "path";
import { TemplateAgents, TemplateFramework } from "../../helpers";
import { TemplateFramework, TemplateUseCase } from "../../helpers";
import { createTestDir, runCreateLlama } from "../utils";
const templateFramework: TemplateFramework = process.env.FRAMEWORK
@@ -12,7 +12,7 @@ const templateFramework: TemplateFramework = process.env.FRAMEWORK
const dataSource: string = process.env.DATASOURCE
? process.env.DATASOURCE
: "--example-file";
const templateAgents: TemplateAgents[] = ["extractor", "contract_review"];
const templateUseCases: TemplateUseCase[] = ["extractor", "contract_review"];
// The reflex template currently only works with FastAPI and files (and not on Windows)
if (
@@ -20,8 +20,8 @@ if (
templateFramework === "fastapi" &&
dataSource === "--example-file"
) {
for (const agents of templateAgents) {
test.describe(`Test reflex template ${agents} ${templateFramework} ${dataSource}`, async () => {
for (const useCase of templateUseCases) {
test.describe(`Test reflex template ${useCase} ${templateFramework} ${dataSource}`, async () => {
let appPort: number;
let name: string;
let appProcess: ChildProcess;
@@ -39,7 +39,7 @@ if (
vectorDb: "none",
port: appPort,
postInstallAction: "runApp",
agents,
useCase,
});
name = result.projectName;
appProcess = result.appProcess;
+4 -4
View File
@@ -33,7 +33,7 @@ export type RunCreateLlamaOptions = {
tools?: string;
useLlamaParse?: boolean;
observability?: string;
agents?: string;
useCase?: string;
};
export async function runCreateLlama({
@@ -51,7 +51,7 @@ export async function runCreateLlama({
tools,
useLlamaParse,
observability,
agents,
useCase,
}: RunCreateLlamaOptions): Promise<CreateLlamaResult> {
if (!process.env.OPENAI_API_KEY || !process.env.LLAMA_CLOUD_API_KEY) {
throw new Error(
@@ -113,8 +113,8 @@ export async function runCreateLlama({
if (observability) {
commandArgs.push("--observability", observability);
}
if ((templateType === "multiagent" || templateType === "reflex") && agents) {
commandArgs.push("--agents", agents);
if ((templateType === "multiagent" || templateType === "reflex") && useCase) {
commandArgs.push("--use-case", useCase);
}
const command = commandArgs.join(" ");
+2 -2
View File
@@ -470,11 +470,11 @@ const getSystemPromptEnv = (
});
const systemPrompt =
"'" +
'"' +
DEFAULT_SYSTEM_PROMPT +
(dataSources?.length ? `\n${DATA_SOURCES_PROMPT}` : "") +
(toolSystemPrompt ? `\n${toolSystemPrompt}` : "") +
"'";
'"';
systemPromptEnv.push({
name: "SYSTEM_PROMPT",
+15 -20
View File
@@ -380,28 +380,32 @@ export const installPythonDependencies = (
};
export const installPythonTemplate = async ({
appName,
root,
template,
framework,
vectorDb,
postInstallAction,
modelConfig,
dataSources,
tools,
postInstallAction,
useLlamaParse,
useCase,
observability,
modelConfig,
agents,
}: Pick<
InstallTemplateArgs,
| "appName"
| "root"
| "framework"
| "template"
| "framework"
| "vectorDb"
| "postInstallAction"
| "modelConfig"
| "dataSources"
| "tools"
| "postInstallAction"
| "useLlamaParse"
| "useCase"
| "observability"
| "modelConfig"
| "agents"
>) => {
console.log("\nInitializing Python project with template:", template, "\n");
let templatePath;
@@ -476,21 +480,12 @@ export const installPythonTemplate = async ({
await copyRouterCode(root, tools ?? []);
}
if (template === "multiagent") {
// Copy multi-agent code
await copy("**", path.join(root), {
parents: true,
cwd: path.join(compPath, "multiagent", "python"),
rename: assetRelocator,
});
}
if (template === "multiagent" || template === "reflex") {
if (agents) {
if (useCase) {
const sourcePath =
template === "multiagent"
? path.join(compPath, "agents", "python", agents)
: path.join(compPath, "reflex", agents);
? path.join(compPath, "agents", "python", useCase)
: path.join(compPath, "reflex", useCase);
await copy("**", path.join(root), {
parents: true,
@@ -500,7 +495,7 @@ export const installPythonTemplate = async ({
} else {
console.log(
red(
`There is no agent selected for ${template} template. Please pick an agent to use via --agents flag.`,
`There is no use case selected for ${template} template. Please pick a use case to use via --use-case flag.`,
),
);
process.exit(1);
+2 -2
View File
@@ -124,7 +124,7 @@ For better results, you can specify the region parameter to get results from a s
dependencies: [
{
name: "e2b_code_interpreter",
version: "0.0.11b38",
version: "1.0.3",
},
],
supportedFrameworks: ["fastapi", "express", "nextjs"],
@@ -155,7 +155,7 @@ For better results, you can specify the region parameter to get results from a s
dependencies: [
{
name: "e2b_code_interpreter",
version: "0.0.11b38",
version: "1.0.3",
},
],
supportedFrameworks: ["fastapi", "express", "nextjs"],
+2 -2
View File
@@ -49,7 +49,7 @@ export type TemplateDataSource = {
};
export type TemplateDataSourceType = "file" | "web" | "db";
export type TemplateObservability = "none" | "traceloop" | "llamatrace";
export type TemplateAgents =
export type TemplateUseCase =
| "financial_report"
| "blog"
| "form_filling"
@@ -106,5 +106,5 @@ export interface InstallTemplateArgs {
postInstallAction?: TemplatePostInstallAction;
tools?: Tool[];
observability?: TemplateObservability;
agents?: TemplateAgents;
useCase?: TemplateUseCase;
}
+9 -9
View File
@@ -26,7 +26,7 @@ export const installTSTemplate = async ({
tools,
dataSources,
useLlamaParse,
agents,
useCase,
}: InstallTemplateArgs & { backend: boolean }) => {
console.log(bold(`Using ${packageManager}.`));
@@ -131,16 +131,16 @@ export const installTSTemplate = async ({
cwd: path.join(multiagentPath, "workflow"),
});
// Copy agents use case code for multiagent template
if (agents) {
console.log("\nCopying agent:", agents, "\n");
const useCasePath = path.join(compPath, "agents", "typescript", agents);
const agentsCodePath = path.join(useCasePath, "workflow");
// Copy use case code for multiagent template
if (useCase) {
console.log("\nCopying use case:", useCase, "\n");
const useCasePath = path.join(compPath, "agents", "typescript", useCase);
const useCaseCodePath = path.join(useCasePath, "workflow");
// Copy agent codes
// Copy use case codes
await copy("**", path.join(root, relativeEngineDestPath, "workflow"), {
parents: true,
cwd: agentsCodePath,
cwd: useCaseCodePath,
rename: assetRelocator,
});
@@ -153,7 +153,7 @@ export const installTSTemplate = async ({
} else {
console.log(
red(
`There is no agent selected for ${template} template. Please pick an agent to use via --agents flag.`,
`There is no use case selected for ${template} template. Please pick a use case to use via --use-case flag.`,
),
);
process.exit(1);
+2 -2
View File
@@ -202,10 +202,10 @@ const program = new Command(packageJson.name)
false,
)
.option(
"--agents <agents>",
"--use-case <useCase>",
`
Select which agents to use for the multi-agent template (e.g: financial_report, blog).
Select which use case to use for the multi-agent template (e.g: financial_report, blog).
`,
)
.allowUnknownOption()
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-llama",
"version": "0.3.23",
"version": "0.3.24",
"description": "Create LlamaIndex-powered apps with one command",
"keywords": [
"rag",
+61 -25
View File
@@ -2,7 +2,7 @@ import { blue } from "picocolors";
import prompts from "prompts";
import { isCI } from ".";
import { COMMUNITY_OWNER, COMMUNITY_REPO } from "../helpers/constant";
import { EXAMPLE_FILE } from "../helpers/datasources";
import { EXAMPLE_FILE, EXAMPLE_GDPR } from "../helpers/datasources";
import { getAvailableLlamapackOptions } from "../helpers/llama-pack";
import { askModelConfig } from "../helpers/providers";
import { getProjectOptions } from "../helpers/repo";
@@ -33,7 +33,7 @@ export const askProQuestions = async (program: QuestionArgs) => {
title: "Multi-agent app (using workflows)",
value: "multiagent",
},
{ title: "Structured Extractor", value: "extractor" },
{ title: "Fullstack python template with Reflex", value: "reflex" },
{
title: `Community template from ${styledRepo}`,
value: "community",
@@ -100,6 +100,24 @@ export const askProQuestions = async (program: QuestionArgs) => {
// So we just use example file for extractor template, this allows user to choose vector database later
program.dataSources = [EXAMPLE_FILE];
program.framework = "fastapi";
// Ask for which Reflex use case to use
const { useCase } = await prompts(
{
type: "select",
name: "useCase",
message: "Which use case would you like to build?",
choices: [
{ title: "Structured Extractor", value: "extractor" },
{
title: "Contract review (using Workflow)",
value: "contract_review",
},
],
initial: 0,
},
questionHandlers,
);
program.useCase = useCase;
}
if (!program.framework) {
@@ -171,32 +189,50 @@ export const askProQuestions = async (program: QuestionArgs) => {
program.observability = observability;
}
// Ask agents
if (program.template === "multiagent" && !program.agents) {
const { agents } = await prompts(
if (
(program.template === "reflex" || program.template === "multiagent") &&
!program.useCase
) {
const choices =
program.template === "reflex"
? [
{ title: "Structured Extractor", value: "extractor" },
{
title: "Contract review (using Workflow)",
value: "contract_review",
},
]
: [
{
title: "Financial report (generate a financial report)",
value: "financial_report",
},
{
title: "Form filling (fill missing value in a CSV file)",
value: "form_filling",
},
{ title: "Blog writer (Write a blog post)", value: "blog" },
];
const { useCase } = await prompts(
{
type: "select",
name: "agents",
message: "Which agents would you like to use?",
choices: [
{
title: "Financial report (generate a financial report)",
value: "financial_report",
},
{
title: "Form filling (fill missing value in a CSV file)",
value: "form_filling",
},
{
title: "Blog writer (Write a blog post)",
value: "blog_writer",
},
],
name: "useCase",
message: "Which use case would you like to use?",
choices,
initial: 0,
},
questionHandlers,
);
program.agents = agents;
program.useCase = useCase;
}
// Configure framework and data sources for Reflex template
if (program.template === "reflex") {
program.framework = "fastapi";
program.dataSources =
program.useCase === "extractor" ? [EXAMPLE_FILE] : [EXAMPLE_GDPR];
}
if (!program.modelConfig) {
@@ -222,8 +258,8 @@ export const askProQuestions = async (program: QuestionArgs) => {
program.vectorDb = vectorDb;
}
if (program.vectorDb === "llamacloud") {
// When using a LlamaCloud index, don't ask for data sources just copy an example file
if (program.vectorDb === "llamacloud" && program.dataSources.length === 0) {
// When using a LlamaCloud index and no data sources are provided, just copy an example file
program.dataSources = [EXAMPLE_FILE];
}
@@ -354,7 +390,7 @@ export const askProQuestions = async (program: QuestionArgs) => {
// default to use LlamaParse if using LlamaCloud
program.useLlamaParse = true;
} else {
// Reflex template doesn't support LlamaParse and LlamaCloud right now (cannot use asyncio loop in Reflex)
// Reflex template doesn't support LlamaParse right now (cannot use asyncio loop in Reflex)
if (program.useLlamaParse === undefined && program.template !== "reflex") {
// if already set useLlamaParse, don't ask again
if (program.dataSources.some((ds) => ds.type === "file")) {
+28 -28
View File
@@ -74,34 +74,34 @@ export const askSimpleQuestions = async (
questionHandlers,
);
language = newLanguage;
}
const { useLlamaCloud: newUseLlamaCloud } = await prompts(
const { useLlamaCloud: newUseLlamaCloud } = await prompts(
{
type: "toggle",
name: "useLlamaCloud",
message: "Do you want to use LlamaCloud services?",
initial: false,
active: "Yes",
inactive: "No",
hint: "see https://www.llamaindex.ai/enterprise for more info",
},
questionHandlers,
);
useLlamaCloud = newUseLlamaCloud;
if (useLlamaCloud && !llamaCloudKey) {
// Ask for LlamaCloud API key, if not set
const { llamaCloudKey: newLlamaCloudKey } = await prompts(
{
type: "toggle",
name: "useLlamaCloud",
message: "Do you want to use LlamaCloud services?",
initial: false,
active: "Yes",
inactive: "No",
hint: "see https://www.llamaindex.ai/enterprise for more info",
type: "text",
name: "llamaCloudKey",
message:
"Please provide your LlamaCloud API key (leave blank to skip):",
},
questionHandlers,
);
useLlamaCloud = newUseLlamaCloud;
if (useLlamaCloud && !llamaCloudKey) {
// Ask for LlamaCloud API key, if not set
const { llamaCloudKey: newLlamaCloudKey } = await prompts(
{
type: "text",
name: "llamaCloudKey",
message:
"Please provide your LlamaCloud API key (leave blank to skip):",
},
questionHandlers,
);
llamaCloudKey = newLlamaCloudKey || process.env.LLAMA_CLOUD_API_KEY;
}
llamaCloudKey = newLlamaCloudKey || process.env.LLAMA_CLOUD_API_KEY;
}
const results = await convertAnswers(args, {
@@ -133,7 +133,7 @@ const convertAnswers = async (
AppType,
Pick<
QuestionResults,
"template" | "tools" | "frontend" | "dataSources" | "agents"
"template" | "tools" | "frontend" | "dataSources" | "useCase"
> & {
modelConfig?: ModelConfig;
}
@@ -160,7 +160,7 @@ const convertAnswers = async (
},
financial_report_agent: {
template: "multiagent",
agents: "financial_report",
useCase: "financial_report",
tools: getTools(["document_generator", "interpreter"]),
dataSources: EXAMPLE_10K_SEC_FILES,
frontend: true,
@@ -168,7 +168,7 @@ const convertAnswers = async (
},
form_filling: {
template: "multiagent",
agents: "form_filling",
useCase: "form_filling",
tools: getTools(["form_filling"]),
dataSources: EXAMPLE_10K_SEC_FILES,
frontend: true,
@@ -176,14 +176,14 @@ const convertAnswers = async (
},
extractor: {
template: "reflex",
agents: "extractor",
useCase: "extractor",
tools: [],
frontend: false,
dataSources: [EXAMPLE_FILE],
},
contract_review: {
template: "reflex",
agents: "contract_review",
useCase: "contract_review",
tools: [],
frontend: false,
dataSources: [EXAMPLE_GDPR],
@@ -5,7 +5,7 @@ import uuid
from typing import List, Optional
from app.services.file import DocumentFile, FileService
from e2b_code_interpreter import CodeInterpreter
from e2b_code_interpreter import Sandbox
from e2b_code_interpreter.models import Logs
from llama_index.core.tools import FunctionTool
from pydantic import BaseModel
@@ -61,7 +61,7 @@ class E2BCodeInterpreter:
Lazily initialize the interpreter.
"""
logger.info(f"Initializing interpreter with {len(sandbox_files)} files")
self.interpreter = CodeInterpreter(api_key=self.api_key)
self.interpreter = Sandbox(api_key=self.api_key)
if len(sandbox_files) > 0:
for file_path in sandbox_files:
file_name = os.path.basename(file_path)
@@ -159,11 +159,11 @@ class E2BCodeInterpreter:
if self.interpreter is None:
self._init_interpreter(sandbox_files)
if self.interpreter and self.interpreter.notebook:
if self.interpreter:
logger.info(
f"\n{'='*50}\n> Running following AI-generated code:\n{code}\n{'='*50}"
)
exec = self.interpreter.notebook.exec_cell(code)
exec = self.interpreter.run_code(code)
if exec.error:
error_message = f"The code failed to execute successfully. Error: {exec.error}. Try to fix the code and run again."
@@ -103,6 +103,7 @@ export class CodeGeneratorTool implements BaseTool<CodeGeneratorParameter> {
const artifact = await this.generateArtifact(
input.requirement,
input.oldCode,
input.sandboxFiles, // help the generated code use exact files
);
if (input.sandboxFiles) {
artifact.files = input.sandboxFiles;
@@ -117,10 +118,12 @@ export class CodeGeneratorTool implements BaseTool<CodeGeneratorParameter> {
async generateArtifact(
query: string,
oldCode?: string,
attachments?: string[],
): Promise<CodeArtifact> {
const userMessage = `
${query}
${oldCode ? `The existing code is: \n\`\`\`${oldCode}\`\`\`` : ""}
${attachments ? `The attachments are: \n${attachments.join("\n")}` : ""}
`;
const messages: ChatMessage[] = [
{ role: "system", content: CODE_GENERATION_PROMPT },
@@ -1,4 +1,4 @@
import { CodeInterpreter, Logs, Result } from "@e2b/code-interpreter";
import { Logs, Result, Sandbox } from "@e2b/code-interpreter";
import type { JSONSchemaType } from "ajv";
import fs from "fs";
import { BaseTool, ToolMetadata } from "llamaindex";
@@ -82,7 +82,7 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> {
private apiKey?: string;
private fileServerURLPrefix?: string;
metadata: ToolMetadata<JSONSchemaType<InterpreterParameter>>;
codeInterpreter?: CodeInterpreter;
codeInterpreter?: Sandbox;
constructor(params?: InterpreterToolParams) {
this.metadata = params?.metadata || DEFAULT_META_DATA;
@@ -104,26 +104,27 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> {
public async initInterpreter(input: InterpreterParameter) {
if (!this.codeInterpreter) {
this.codeInterpreter = await CodeInterpreter.create({
this.codeInterpreter = await Sandbox.create({
apiKey: this.apiKey,
});
}
// upload files to sandbox
if (input.sandboxFiles) {
console.log(`Uploading ${input.sandboxFiles.length} files to sandbox`);
try {
for (const filePath of input.sandboxFiles) {
const fileName = path.basename(filePath);
const localFilePath = path.join(this.uploadedFilesDir, fileName);
const content = fs.readFileSync(localFilePath);
// upload files to sandbox when it's initialized
if (input.sandboxFiles) {
console.log(`Uploading ${input.sandboxFiles.length} files to sandbox`);
try {
for (const filePath of input.sandboxFiles) {
const fileName = path.basename(filePath);
const localFilePath = path.join(this.uploadedFilesDir, fileName);
const content = fs.readFileSync(localFilePath);
const arrayBuffer = new Uint8Array(content).buffer;
await this.codeInterpreter?.files.write(filePath, arrayBuffer);
const arrayBuffer = new Uint8Array(content).buffer;
await this.codeInterpreter?.files.write(filePath, arrayBuffer);
}
} catch (error) {
console.error("Got error when uploading files to sandbox", error);
}
} catch (error) {
console.error("Got error when uploading files to sandbox", error);
}
}
return this.codeInterpreter;
}
@@ -150,7 +151,7 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> {
`\n${"=".repeat(50)}\n> Running following AI-generated code:\n${input.code}\n${"=".repeat(50)}`,
);
const interpreter = await this.initInterpreter(input);
const exec = await interpreter.notebook.execCell(input.code);
const exec = await interpreter.runCode(input.code);
if (exec.error) console.error("[Code Interpreter error]", exec.error);
const extraResult = await this.getExtraResult(exec.results[0]);
const result: InterpreterToolOutput = {
@@ -169,7 +170,7 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> {
}
async close() {
await this.codeInterpreter?.close();
await this.codeInterpreter?.kill();
}
private async getExtraResult(
+25 -28
View File
@@ -17,11 +17,11 @@ import logging
import os
import uuid
from dataclasses import asdict
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional
from app.engine.tools.artifact import CodeArtifact
from app.services.file import FileService
from e2b_code_interpreter import CodeInterpreter, Sandbox # type: ignore
from e2b_code_interpreter import Sandbox # type: ignore
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel
@@ -60,6 +60,14 @@ class FileUpload(BaseModel):
name: str
SUPPORTED_TEMPLATES = [
"nextjs-developer",
"vue-developer",
"streamlit-developer",
"gradio-developer",
]
@sandbox_router.post("")
async def create_sandbox(request: Request):
request_data = await request.json()
@@ -79,33 +87,22 @@ async def create_sandbox(request: Request):
status_code=400, detail="Could not create artifact from the request data"
)
sbx = None
# Create an interpreter or a sandbox
if artifact.template == "code-interpreter-multilang":
sbx = CodeInterpreter(api_key=os.getenv("E2B_API_KEY"), timeout=SANDBOX_TIMEOUT)
logger.debug(f"Created code interpreter {sbx}")
else:
sbx = Sandbox(
api_key=os.getenv("E2B_API_KEY"),
template=artifact.template,
metadata={"template": artifact.template, "user_id": "default"},
timeout=SANDBOX_TIMEOUT,
)
logger.debug(f"Created sandbox {sbx}")
sbx = Sandbox(
api_key=os.getenv("E2B_API_KEY"),
template=(
None if artifact.template not in SUPPORTED_TEMPLATES else artifact.template
),
metadata={"template": artifact.template, "user_id": "default"},
timeout=SANDBOX_TIMEOUT,
)
logger.debug(f"Created sandbox {sbx}")
# Install packages
if artifact.has_additional_dependencies:
if isinstance(sbx, CodeInterpreter):
sbx.notebook.exec_cell(artifact.install_dependencies_command)
logger.debug(
f"Installed dependencies: {', '.join(artifact.additional_dependencies)} in code interpreter {sbx}"
)
elif isinstance(sbx, Sandbox):
sbx.commands.run(artifact.install_dependencies_command)
logger.debug(
f"Installed dependencies: {', '.join(artifact.additional_dependencies)} in sandbox {sbx}"
)
sbx.commands.run(artifact.install_dependencies_command)
logger.debug(
f"Installed dependencies: {', '.join(artifact.additional_dependencies)} in sandbox {sbx}"
)
# Copy files
if len(sandbox_files) > 0:
@@ -122,7 +119,7 @@ async def create_sandbox(request: Request):
# Execute code or return a URL to the running sandbox
if artifact.template == "code-interpreter-multilang":
result = sbx.notebook.exec_cell(artifact.code or "")
result = sbx.run_code(artifact.code or "")
output_urls = _download_cell_results(result.results)
runtime_error = asdict(result.error) if result.error else None
return ExecutionResult(
@@ -145,7 +142,7 @@ async def create_sandbox(request: Request):
def _upload_files(
sandbox: Union[CodeInterpreter, Sandbox],
sandbox: Sandbox,
sandbox_files: List[str] = [],
) -> None:
for file_path in sandbox_files:
@@ -0,0 +1,65 @@
import logging
import os
from typing import Any, Dict, List, Optional
from llama_index.core.schema import NodeWithScore
from pydantic import BaseModel
from app.config import DATA_DIR
logger = logging.getLogger("uvicorn")
class SourceNodes(BaseModel):
id: str
metadata: Dict[str, Any]
score: Optional[float]
text: str
url: Optional[str]
@classmethod
def from_source_node(cls, source_node: NodeWithScore):
metadata = source_node.node.metadata
url = cls.get_url_from_metadata(metadata)
return cls(
id=source_node.node.node_id,
metadata=metadata,
score=source_node.score,
text=source_node.node.text, # type: ignore
url=url,
)
@classmethod
def get_url_from_metadata(cls, metadata: Dict[str, Any]) -> Optional[str]:
url_prefix = os.getenv("FILESERVER_URL_PREFIX")
if not url_prefix:
logger.warning(
"Warning: FILESERVER_URL_PREFIX not set in environment variables. Can't use file server"
)
file_name = metadata.get("file_name")
if file_name and url_prefix:
# file_name exists and file server is configured
pipeline_id = metadata.get("pipeline_id")
if pipeline_id:
# file is from LlamaCloud
file_name = f"{pipeline_id}${file_name}"
return f"{url_prefix}/output/llamacloud/{file_name}"
is_private = metadata.get("private", "false") == "true"
if is_private:
# file is a private upload
return f"{url_prefix}/output/uploaded/{file_name}"
# file is from calling the 'generate' script
# Get the relative path of file_path to data_dir
file_path = metadata.get("file_path")
data_dir = os.path.abspath(DATA_DIR)
if file_path and data_dir:
relative_path = os.path.relpath(file_path, data_dir)
return f"{url_prefix}/data/{relative_path}"
# fallback to URL in metadata (e.g. for websites)
return metadata.get("URL")
@classmethod
def from_source_nodes(cls, source_nodes: List[NodeWithScore]):
return [cls.from_source_node(node) for node in source_nodes]
+281
View File
@@ -0,0 +1,281 @@
import base64
import logging
import mimetypes
import os
import re
import uuid
from io import BytesIO
from pathlib import Path
from typing import List, Optional, Tuple
from llama_index.core import VectorStoreIndex
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.readers.file.base import (
_try_loading_included_file_formats as get_file_loaders_map,
)
from llama_index.core.schema import Document
from llama_index.indices.managed.llama_cloud.base import LlamaCloudIndex
from llama_index.readers.file import FlatReader
from pydantic import BaseModel, Field
logger = logging.getLogger(__name__)
PRIVATE_STORE_PATH = str(Path("output", "uploaded"))
TOOL_STORE_PATH = str(Path("output", "tools"))
LLAMA_CLOUD_STORE_PATH = str(Path("output", "llamacloud"))
class DocumentFile(BaseModel):
id: str
name: str # Stored file name
type: str = None
size: int = None
url: str = None
path: Optional[str] = Field(
None,
description="The stored file path. Used internally in the server.",
exclude=True,
)
refs: Optional[List[str]] = Field(
None, description="The document ids in the index."
)
class FileService:
"""
To store the files uploaded by the user and add them to the index.
"""
@classmethod
def process_private_file(
cls,
file_name: str,
base64_content: str,
params: Optional[dict] = None,
) -> DocumentFile:
"""
Store the uploaded file and index it if necessary.
"""
try:
from app.engine.index import IndexConfig, get_index
except ImportError as e:
raise ValueError("IndexConfig or get_index is not found") from e
if params is None:
params = {}
# Add the nodes to the index and persist it
index_config = IndexConfig(**params)
index = get_index(index_config)
# Preprocess and store the file
file_data, extension = cls._preprocess_base64_file(base64_content)
document_file = cls.save_file(
file_data,
file_name=file_name,
save_dir=PRIVATE_STORE_PATH,
)
# Don't index csv files (they are handled by tools)
if extension == "csv":
return document_file
else:
# Insert the file into the index and update document ids to the file metadata
if isinstance(index, LlamaCloudIndex):
doc_id = cls._add_file_to_llama_cloud_index(
index, document_file.name, file_data
)
# Add document ids to the file metadata
document_file.refs = [doc_id]
else:
documents = cls._load_file_to_documents(document_file)
cls._add_documents_to_vector_store_index(documents, index)
# Add document ids to the file metadata
document_file.refs = [doc.doc_id for doc in documents]
# Return the file metadata
return document_file
@classmethod
def save_file(
cls,
content: bytes | str,
file_name: str,
save_dir: Optional[str] = None,
) -> DocumentFile:
"""
Save the content to a file in the local file server (accessible via URL)
Args:
content (bytes | str): The content to save, either bytes or string.
file_name (str): The original name of the file.
save_dir (Optional[str]): The relative path from the current working directory. Defaults to the `output/uploaded` directory.
Returns:
The metadata of the saved file.
"""
if save_dir is None:
save_dir = os.path.join("output", "uploaded")
file_id = str(uuid.uuid4())
name, extension = os.path.splitext(file_name)
extension = extension.lstrip(".")
sanitized_name = _sanitize_file_name(name)
if extension == "":
raise ValueError("File is not supported!")
new_file_name = f"{sanitized_name}_{file_id}.{extension}"
file_path = os.path.join(save_dir, new_file_name)
if isinstance(content, str):
content = content.encode()
try:
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, "wb") as file:
file.write(content)
except PermissionError as e:
logger.error(
f"Permission denied when writing to file {file_path}: {str(e)}"
)
raise
except IOError as e:
logger.error(
f"IO error occurred when writing to file {file_path}: {str(e)}"
)
raise
except Exception as e:
logger.error(f"Unexpected error when writing to file {file_path}: {str(e)}")
raise
logger.info(f"Saved file to {file_path}")
file_url_prefix = os.getenv("FILESERVER_URL_PREFIX")
if file_url_prefix is None:
logger.warning(
"FILESERVER_URL_PREFIX is not set, fallback to http://localhost:8000/api/files"
)
file_url_prefix = "http://localhost:8000/api/files"
file_size = os.path.getsize(file_path)
file_url = os.path.join(
file_url_prefix,
save_dir,
new_file_name,
)
return DocumentFile(
id=file_id,
name=new_file_name,
type=extension,
size=file_size,
path=file_path,
url=file_url,
refs=None,
)
@staticmethod
def _preprocess_base64_file(base64_content: str) -> Tuple[bytes, str | None]:
header, data = base64_content.split(",", 1)
mime_type = header.split(";")[0].split(":", 1)[1]
extension = mimetypes.guess_extension(mime_type).lstrip(".")
# File data as bytes
return base64.b64decode(data), extension
@staticmethod
def _load_file_to_documents(file: DocumentFile) -> List[Document]:
"""
Load the file from the private directory and return the documents
"""
_, extension = os.path.splitext(file.name)
extension = extension.lstrip(".")
# Load file to documents
# If LlamaParse is enabled, use it to parse the file
# Otherwise, use the default file loaders
reader = _get_llamaparse_parser()
if reader is None:
reader_cls = _default_file_loaders_map().get(f".{extension}")
if reader_cls is None:
raise ValueError(f"File extension {extension} is not supported")
reader = reader_cls()
if file.path is None:
raise ValueError("Document file path is not set")
documents = reader.load_data(Path(file.path))
# Add custom metadata
for doc in documents:
doc.metadata["file_name"] = file.name
doc.metadata["private"] = "true"
return documents
@staticmethod
def _add_documents_to_vector_store_index(
documents: List[Document], index: VectorStoreIndex
) -> None:
"""
Add the documents to the vector store index
"""
pipeline = IngestionPipeline()
nodes = pipeline.run(documents=documents)
# Add the nodes to the index and persist it
if index is None:
index = VectorStoreIndex(nodes=nodes)
else:
index.insert_nodes(nodes=nodes)
index.storage_context.persist(
persist_dir=os.environ.get("STORAGE_DIR", "storage")
)
@staticmethod
def _add_file_to_llama_cloud_index(
index: LlamaCloudIndex,
file_name: str,
file_data: bytes,
) -> str:
"""
Add the file to the LlamaCloud index.
LlamaCloudIndex is a managed index so we can directly use the files.
"""
try:
from app.engine.service import LLamaCloudFileService # type: ignore
except ImportError as e:
raise ValueError("LlamaCloudFileService is not found") from e
# LlamaCloudIndex is a managed index so we can directly use the files
upload_file = (file_name, BytesIO(file_data))
doc_id = LLamaCloudFileService.add_file_to_pipeline(
index.project.id,
index.pipeline.id,
upload_file,
custom_metadata={},
wait_for_processing=True,
)
return doc_id
def _sanitize_file_name(file_name: str) -> str:
"""
Sanitize the file name by replacing all non-alphanumeric characters with underscores
"""
sanitized_name = re.sub(r"[^a-zA-Z0-9.]", "_", file_name)
return sanitized_name
def _get_llamaparse_parser():
from app.engine.loaders import load_configs
from app.engine.loaders.file import FileLoaderConfig, llama_parse_parser
config = load_configs()
file_loader_config = FileLoaderConfig(**config["file"])
if file_loader_config.use_llama_parse:
return llama_parse_parser()
else:
return None
def _default_file_loaders_map():
default_loaders = get_file_loaders_map()
default_loaders[".txt"] = FlatReader
default_loaders[".csv"] = FlatReader
return default_loaders
@@ -24,7 +24,7 @@
"llamaindex": "0.8.2",
"pdf2json": "3.0.5",
"ajv": "^8.12.0",
"@e2b/code-interpreter": "0.0.9-beta.3",
"@e2b/code-interpreter": "1.0.4",
"got": "^14.4.1",
"@apidevtools/swagger-parser": "^10.1.0",
"formdata-node": "^6.0.3",
@@ -13,13 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
CodeInterpreter,
ExecutionError,
Result,
Sandbox,
} from "@e2b/code-interpreter";
import { ExecutionError, Result, Sandbox } from "@e2b/code-interpreter";
import { Request, Response } from "express";
import path from "node:path";
import { saveDocument } from "./llamaindex/documents/helper";
type CodeArtifact = {
@@ -39,6 +35,8 @@ const sandboxTimeout = 10 * 60 * 1000; // 10 minute in ms
export const maxDuration = 60;
const OUTPUT_DIR = path.join("output", "tools");
export type ExecutionResult = {
template: string;
stdout: string[];
@@ -48,60 +46,53 @@ export type ExecutionResult = {
url: string;
};
// see https://github.com/e2b-dev/fragments/tree/main/sandbox-templates
const SUPPORTED_TEMPLATES = [
"nextjs-developer",
"vue-developer",
"streamlit-developer",
"gradio-developer",
];
export const sandbox = async (req: Request, res: Response) => {
const { artifact }: { artifact: CodeArtifact } = req.body;
let sbx: Sandbox | CodeInterpreter | undefined = undefined;
// Create a interpreter or a sandbox
if (artifact.template === "code-interpreter-multilang") {
sbx = await CodeInterpreter.create({
metadata: { template: artifact.template },
timeoutMs: sandboxTimeout,
});
console.log("Created code interpreter", sbx.sandboxID);
let sbx: Sandbox;
const sandboxOpts = {
metadata: { template: artifact.template, userID: "default" },
timeoutMs: sandboxTimeout,
};
if (SUPPORTED_TEMPLATES.includes(artifact.template)) {
sbx = await Sandbox.create(artifact.template, sandboxOpts);
} else {
sbx = await Sandbox.create(artifact.template, {
metadata: { template: artifact.template, userID: "default" },
timeoutMs: sandboxTimeout,
});
console.log("Created sandbox", sbx.sandboxID);
sbx = await Sandbox.create(sandboxOpts);
}
console.log("Created sandbox", sbx.sandboxId);
// Install packages
if (artifact.has_additional_dependencies) {
if (sbx instanceof CodeInterpreter) {
await sbx.notebook.execCell(artifact.install_dependencies_command);
console.log(
`Installed dependencies: ${artifact.additional_dependencies.join(", ")} in code interpreter ${sbx.sandboxID}`,
);
} else if (sbx instanceof Sandbox) {
await sbx.commands.run(artifact.install_dependencies_command);
console.log(
`Installed dependencies: ${artifact.additional_dependencies.join(", ")} in sandbox ${sbx.sandboxID}`,
);
}
await sbx.commands.run(artifact.install_dependencies_command);
console.log(
`Installed dependencies: ${artifact.additional_dependencies.join(", ")} in sandbox ${sbx.sandboxId}`,
);
}
// Copy code to fs
if (artifact.code && Array.isArray(artifact.code)) {
artifact.code.forEach(async (file) => {
await sbx.files.write(file.file_path, file.file_content);
console.log(`Copied file to ${file.file_path} in ${sbx.sandboxID}`);
console.log(`Copied file to ${file.file_path} in ${sbx.sandboxId}`);
});
} else {
await sbx.files.write(artifact.file_path, artifact.code);
console.log(`Copied file to ${artifact.file_path} in ${sbx.sandboxID}`);
console.log(`Copied file to ${artifact.file_path} in ${sbx.sandboxId}`);
}
// Execute code or return a URL to the running sandbox
if (artifact.template === "code-interpreter-multilang") {
const result = await (sbx as CodeInterpreter).notebook.execCell(
artifact.code || "",
);
await (sbx as CodeInterpreter).close();
const result = await sbx.runCode(artifact.code || "");
await sbx.kill();
const outputUrls = await downloadCellResults(result.results);
return res.status(200).json({
template: artifact.template,
stdout: result.logs.stdout,
@@ -121,17 +112,23 @@ async function downloadCellResults(
cellResults?: Result[],
): Promise<Array<{ url: string; filename: string }>> {
if (!cellResults) return [];
const results = await Promise.all(
cellResults.map(async (res) => {
const formats = res.formats(); // available formats in the result
const formatResults = await Promise.all(
formats.map(async (ext) => {
const filename = `${crypto.randomUUID()}.${ext}`;
const base64 = res[ext as keyof Result];
const buffer = Buffer.from(base64, "base64");
const fileurl = await saveDocument(filename, buffer);
return { url: fileurl, filename };
}),
formats
.filter((ext) => ["png", "svg", "jpeg", "pdf"].includes(ext))
.map(async (ext) => {
const filename = `${crypto.randomUUID()}.${ext}`;
const base64 = res[ext as keyof Result];
const buffer = Buffer.from(base64, "base64");
const fileurl = await saveDocument(
path.join(OUTPUT_DIR, filename),
buffer,
);
return { url: fileurl, filename };
}),
);
return formatResults;
}),
+65 -28
View File
@@ -14,10 +14,33 @@ dotenv.load_dotenv()
FRONTEND_DIR = Path(os.getenv("FRONTEND_DIR", ".frontend"))
DEFAULT_FRONTEND_PORT = 3000
APP_HOST = os.getenv("APP_HOST", "localhost")
APP_PORT = int(
os.getenv("APP_PORT", 8000)
) # Allocated to backend but also for access to the app, please change it in .env
DEFAULT_FRONTEND_PORT = (
3000 # Not for access directly, but for proxying to the backend in development
)
STATIC_DIR = Path(os.getenv("STATIC_DIR", "static"))
class NodePackageManager(str):
def __new__(cls, value: str) -> "NodePackageManager":
return super().__new__(cls, value)
@property
def name(self) -> str:
return Path(self).stem
@property
def is_pnpm(self) -> bool:
return self.name == "pnpm"
@property
def is_npm(self) -> bool:
return self.name == "npm"
def build():
"""
Build the frontend and copy the static files to the backend.
@@ -146,22 +169,20 @@ async def _run_frontend(
package_manager,
"run",
"dev",
"--" if package_manager.is_npm else "",
"-p",
str(port),
cwd=FRONTEND_DIR,
)
rich.print(
f"\n[bold]Waiting for frontend to start, port: {port}, process id: {frontend_process.pid}[/bold]"
)
rich.print("\n[bold]Waiting for frontend to start...")
# Block until the frontend is accessible
for _ in range(timeout):
await asyncio.sleep(1)
# Check if the frontend is accessible (port is open) or frontend_process is running
if frontend_process.returncode is not None:
raise RuntimeError("Could not start frontend dev server")
if not _is_bindable_port(port):
if _is_server_running(port):
rich.print(
f"\n[bold green]Frontend dev server is running on port {port}[/bold green]"
"\n[bold]Frontend dev server is running. Please wait a while for the app to be ready...[/bold]"
)
return frontend_process, port
raise TimeoutError(f"Frontend dev server failed to start within {timeout} seconds")
@@ -173,33 +194,50 @@ async def _run_backend(
"""
Start the backend development server.
Args:
frontend_port: The port number the frontend is running on
Returns:
Process: The backend process
"""
# Merge environment variables
envs = {**os.environ, **(envs or {})}
rich.print("\n[bold]Starting backend FastAPI server...[/bold]")
# Check if the port is free
if not _is_port_available(APP_PORT):
raise SystemError(
f"Port {APP_PORT} is not available! Please change the port in .env file or kill the process running on this port."
)
rich.print(f"\n[bold]Starting app on port {APP_PORT}...[/bold]")
poetry_executable = _get_poetry_executable()
return await asyncio.create_subprocess_exec(
process = await asyncio.create_subprocess_exec(
poetry_executable,
"run",
"python",
"main.py",
env=envs,
)
# Wait for port is started
timeout = 30
for _ in range(timeout):
await asyncio.sleep(1)
if process.returncode is not None:
raise RuntimeError("Could not start backend dev server")
if _is_server_running(APP_PORT):
rich.print(
f"\n[bold green]App is running. You now can access it at http://{APP_HOST}:{APP_PORT}[/bold green]"
)
return process
# Timeout, kill the process
process.terminate()
raise TimeoutError(f"Backend dev server failed to start within {timeout} seconds")
def _install_frontend_dependencies():
package_manager = _get_node_package_manager()
rich.print(
f"\n[bold]Installing frontend dependencies using {Path(package_manager).name}. It might take a while...[/bold]"
f"\n[bold]Installing frontend dependencies using {package_manager.name}. It might take a while...[/bold]"
)
run([package_manager, "install"], cwd=".frontend", check=True)
def _get_node_package_manager() -> str:
def _get_node_package_manager() -> NodePackageManager:
"""
Check for available package managers and return the preferred one.
Returns 'pnpm' if installed, falls back to 'npm'.
@@ -215,12 +253,12 @@ def _get_node_package_manager() -> str:
for cmd in pnpm_cmds:
cmd_path = which(cmd)
if cmd_path is not None:
return cmd_path
return NodePackageManager(cmd_path)
for cmd in npm_cmds:
cmd_path = which(cmd)
if cmd_path is not None:
return cmd_path
return NodePackageManager(cmd_path)
raise SystemError(
"Neither pnpm nor npm is installed. Please install Node.js and a package manager first."
@@ -244,28 +282,27 @@ def _get_poetry_executable() -> str:
raise SystemError("Poetry is not installed. Please install Poetry first.")
def _is_bindable_port(port: int) -> bool:
"""Check if a port is available by attempting to connect to it."""
def _is_port_available(port: int) -> bool:
"""Check if a port is available for binding."""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
# Try to connect to the port
s.connect(("localhost", port))
# If we can connect, port is in use
return False
return False # Port is in use, so not available
except ConnectionRefusedError:
# Connection refused means port is available
return True
return True # Port is available
except socket.error:
# Other socket errors also likely mean port is available
return True
return True # Other socket errors likely mean port is available
def _is_server_running(port: int) -> bool:
"""Check if a server is running on the specified port."""
return not _is_port_available(port)
def _find_free_port(start_port: int) -> int:
"""
Find a free port starting from the given port number.
"""
"""Find a free port starting from the given port number."""
for port in range(start_port, 65535):
if _is_bindable_port(port):
if _is_port_available(port):
return port
raise SystemError("No free port found")
@@ -13,12 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
CodeInterpreter,
ExecutionError,
Result,
Sandbox,
} from "@e2b/code-interpreter";
import { ExecutionError, Result, Sandbox } from "@e2b/code-interpreter";
import fs from "node:fs/promises";
import path from "node:path";
import { saveDocument } from "../chat/llamaindex/documents/helper";
@@ -41,6 +36,8 @@ const sandboxTimeout = 10 * 60 * 1000; // 10 minute in ms
export const maxDuration = 60;
const OUTPUT_DIR = path.join("output", "tools");
export type ExecutionResult = {
template: string;
stdout: string[];
@@ -50,39 +47,35 @@ export type ExecutionResult = {
url: string;
};
// see https://github.com/e2b-dev/fragments/tree/main/sandbox-templates
const SUPPORTED_TEMPLATES = [
"nextjs-developer",
"vue-developer",
"streamlit-developer",
"gradio-developer",
];
export async function POST(req: Request) {
const { artifact }: { artifact: CodeArtifact } = await req.json();
let sbx: Sandbox | CodeInterpreter | undefined = undefined;
// Create a interpreter or a sandbox
if (artifact.template === "code-interpreter-multilang") {
sbx = await CodeInterpreter.create({
metadata: { template: artifact.template },
timeoutMs: sandboxTimeout,
});
console.log("Created code interpreter", sbx.sandboxID);
let sbx: Sandbox;
const sandboxOpts = {
metadata: { template: artifact.template, userID: "default" },
timeoutMs: sandboxTimeout,
};
if (SUPPORTED_TEMPLATES.includes(artifact.template)) {
sbx = await Sandbox.create(artifact.template, sandboxOpts);
} else {
sbx = await Sandbox.create(artifact.template, {
metadata: { template: artifact.template, userID: "default" },
timeoutMs: sandboxTimeout,
});
console.log("Created sandbox", sbx.sandboxID);
sbx = await Sandbox.create(sandboxOpts);
}
console.log("Created sandbox", sbx.sandboxId);
// Install packages
if (artifact.has_additional_dependencies) {
if (sbx instanceof CodeInterpreter) {
await sbx.notebook.execCell(artifact.install_dependencies_command);
console.log(
`Installed dependencies: ${artifact.additional_dependencies.join(", ")} in code interpreter ${sbx.sandboxID}`,
);
} else if (sbx instanceof Sandbox) {
await sbx.commands.run(artifact.install_dependencies_command);
console.log(
`Installed dependencies: ${artifact.additional_dependencies.join(", ")} in sandbox ${sbx.sandboxID}`,
);
}
await sbx.commands.run(artifact.install_dependencies_command);
console.log(
`Installed dependencies: ${artifact.additional_dependencies.join(", ")} in sandbox ${sbx.sandboxId}`,
);
}
// Copy files
@@ -94,7 +87,7 @@ export async function POST(req: Request) {
const arrayBuffer = new Uint8Array(fileContent).buffer;
await sbx.files.write(sandboxFilePath, arrayBuffer);
console.log(`Copied file to ${sandboxFilePath} in ${sbx.sandboxID}`);
console.log(`Copied file to ${sandboxFilePath} in ${sbx.sandboxId}`);
});
}
@@ -102,19 +95,17 @@ export async function POST(req: Request) {
if (artifact.code && Array.isArray(artifact.code)) {
artifact.code.forEach(async (file) => {
await sbx.files.write(file.file_path, file.file_content);
console.log(`Copied file to ${file.file_path} in ${sbx.sandboxID}`);
console.log(`Copied file to ${file.file_path} in ${sbx.sandboxId}`);
});
} else {
await sbx.files.write(artifact.file_path, artifact.code);
console.log(`Copied file to ${artifact.file_path} in ${sbx.sandboxID}`);
console.log(`Copied file to ${artifact.file_path} in ${sbx.sandboxId}`);
}
// Execute code or return a URL to the running sandbox
if (artifact.template === "code-interpreter-multilang") {
const result = await (sbx as CodeInterpreter).notebook.execCell(
artifact.code || "",
);
await (sbx as CodeInterpreter).close();
const result = await sbx.runCode(artifact.code || "");
await sbx.kill();
const outputUrls = await downloadCellResults(result.results);
return new Response(
JSON.stringify({
@@ -139,17 +130,23 @@ async function downloadCellResults(
cellResults?: Result[],
): Promise<Array<{ url: string; filename: string }>> {
if (!cellResults) return [];
const results = await Promise.all(
cellResults.map(async (res) => {
const formats = res.formats(); // available formats in the result
const formatResults = await Promise.all(
formats.map(async (ext) => {
const filename = `${crypto.randomUUID()}.${ext}`;
const base64 = res[ext as keyof Result];
const buffer = Buffer.from(base64, "base64");
const fileurl = await saveDocument(filename, buffer);
return { url: fileurl, filename };
}),
formats
.filter((ext) => ["png", "svg", "jpeg", "pdf"].includes(ext))
.map(async (ext) => {
const filename = `${crypto.randomUUID()}.${ext}`;
const base64 = res[ext as keyof Result];
const buffer = Buffer.from(base64, "base64");
const fileurl = await saveDocument(
path.join(OUTPUT_DIR, filename),
buffer,
);
return { url: fileurl, filename };
}),
);
return formatResults;
}),
@@ -11,7 +11,7 @@
},
"dependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
"@e2b/code-interpreter": "0.0.9-beta.3",
"@e2b/code-interpreter": "1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-slot": "^1.0.2",