-
Notifications
You must be signed in to change notification settings - Fork 113
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
Support uploading file to e2b code interpreter tool #113
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ | |
import base64 | ||
import uuid | ||
from pydantic import BaseModel | ||
from typing import List, Tuple, Dict | ||
from typing import List, Tuple, Dict, Optional | ||
from llama_index.core.tools import FunctionTool | ||
from e2b_code_interpreter import CodeInterpreter | ||
from e2b_code_interpreter.models import Logs | ||
|
@@ -14,14 +14,15 @@ | |
|
||
class InterpreterExtraResult(BaseModel): | ||
type: str | ||
filename: str | ||
url: str | ||
content: Optional[str] = None | ||
filename: Optional[str] = None | ||
url: Optional[str] = None | ||
|
||
|
||
class E2BToolOutput(BaseModel): | ||
is_error: bool | ||
logs: Logs | ||
results: List[InterpreterExtraResult] = [] | ||
results: List[InterpreterExtraResult | str] = [] | ||
|
||
|
||
class E2BCodeInterpreter: | ||
|
@@ -62,8 +63,9 @@ def get_file_url(self, filename: str) -> str: | |
|
||
def parse_result(self, result) -> List[InterpreterExtraResult]: | ||
""" | ||
The result could include multiple formats (e.g. png, svg, etc.) but encoded in base64 | ||
We save each result to disk and return saved file metadata (extension, filename, url) | ||
The result format could be either a base64 string (png, svg, etc.) or a raw text (text, html, markdown,...) | ||
If it's base64, we save each result to disk and return saved file metadata (extension, filename, url), | ||
otherwise just return the raw text content | ||
""" | ||
if not result: | ||
return [] | ||
|
@@ -72,31 +74,46 @@ def parse_result(self, result) -> List[InterpreterExtraResult]: | |
|
||
try: | ||
formats = result.formats() | ||
base64_data_arr = [result[format] for format in formats] | ||
|
||
for ext, base64_data in zip(formats, base64_data_arr): | ||
if ext and base64_data: | ||
result = self.save_to_disk(base64_data, ext) | ||
filename = result["filename"] | ||
output.append( | ||
InterpreterExtraResult( | ||
type=ext, filename=filename, url=self.get_file_url(filename) | ||
data_list = [result[format] for format in formats] | ||
|
||
for ext, data in zip(formats, data_list): | ||
match ext: | ||
case "png" | "jpeg" | "svg": | ||
result = self.save_to_disk(data, ext) | ||
filename = result["filename"] | ||
output.append( | ||
InterpreterExtraResult( | ||
type=ext, | ||
filename=filename, | ||
url=self.get_file_url(filename), | ||
) | ||
) | ||
) | ||
break | ||
case "text" | "html" | "markdown": | ||
output.append(InterpreterExtraResult(type=ext, content=data)) | ||
except Exception as error: | ||
logger.error("Error when saving data to disk", error) | ||
|
||
return output | ||
|
||
def interpret(self, code: str) -> E2BToolOutput: | ||
def interpret(self, code: str, file_path: Optional[str] = None) -> E2BToolOutput: | ||
with CodeInterpreter(api_key=self.api_key) as interpreter: | ||
# Upload file to E2B sandbox | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Always upload the file for each code interpreter calling. We can improve this by introducing a chat session feature, then each session can use its own sandbox environment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. makes sense. I think this is a general concept we have to add later, e.g. we also need to store the agent's memory in that session store |
||
if file_path is not None: | ||
with open(file_path, "rb") as f: | ||
remote_path = interpreter.upload_file(f) | ||
|
||
# Execute the code to analyze the file | ||
logger.info( | ||
f"\n{'='*50}\n> Running following AI-generated code:\n{code}\n{'='*50}" | ||
) | ||
exec = interpreter.notebook.exec_cell(code) | ||
|
||
if exec.error: | ||
output = E2BToolOutput(is_error=True, logs=[exec.error]) | ||
logger.error( | ||
f"Error when executing code in E2B sandbox: {exec.error} {exec.logs}" | ||
) | ||
output = E2BToolOutput(is_error=True, logs=exec.logs, results=[]) | ||
else: | ||
if len(exec.results) == 0: | ||
output = E2BToolOutput(is_error=False, logs=exec.logs, results=[]) | ||
|
@@ -108,9 +125,15 @@ def interpret(self, code: str) -> E2BToolOutput: | |
return output | ||
|
||
|
||
def code_interpret(code: str) -> Dict: | ||
def code_interpret(code: str, local_file_path: str) -> Dict: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is for the case that the user doesn't upload a file? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also thought about that, we can use local_file_path as optional argument for the case the code does not use any data, but that doesn't seem practical. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what do you mean by "not practical"? |
||
""" | ||
Execute python code in a Jupyter notebook cell and return any result, stdout, stderr, display_data, and error. | ||
Use this tool to analyze the provided data in a sandbox environment. | ||
The tool will: | ||
1. Upload the provided file from local to the sandbox. The uploaded file path will be /home/user/{filename} | ||
2. Execute python code in a Jupyter notebook cell to analyze the uploaded file in the sandbox. | ||
3. Get the result from the code in stdout, stderr, display_data, and error. | ||
You must to provide the code and the provided file path to run this tool. | ||
Your code should read the file from the sandbox path /home/user/{filename}. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An additional note for AI to use the path in the sandbox environment |
||
""" | ||
api_key = os.getenv("E2B_API_KEY") | ||
filesever_url_prefix = os.getenv("FILESERVER_URL_PREFIX") | ||
|
@@ -126,7 +149,7 @@ def code_interpret(code: str) -> Dict: | |
interpreter = E2BCodeInterpreter( | ||
api_key=api_key, filesever_url_prefix=filesever_url_prefix | ||
) | ||
output = interpreter.interpret(code) | ||
output = interpreter.interpret(code, local_file_path) | ||
return output.dict() | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import path from "node:path"; | |
|
||
export type InterpreterParameter = { | ||
code: string; | ||
localFilePath: string; | ||
}; | ||
|
||
export type InterpreterToolParams = { | ||
|
@@ -34,21 +35,32 @@ type InterpreterExtraType = | |
|
||
export type InterpreterExtraResult = { | ||
type: InterpreterExtraType; | ||
content?: string; | ||
filename: string; | ||
url: string; | ||
}; | ||
|
||
const DEFAULT_META_DATA: ToolMetadata<JSONSchemaType<InterpreterParameter>> = { | ||
name: "interpreter", | ||
description: | ||
"Execute python code in a Jupyter notebook cell and return any result, stdout, stderr, display_data, and error.", | ||
description: `Use this tool to analyze the provided data in a sandbox environment. | ||
The tool will: | ||
1. Upload the provided file from local to the sandbox. The uploaded file path will be /home/user/{filename} | ||
2. Execute python code in a Jupyter notebook cell to analyze the uploaded file in the sandbox. | ||
3. Get the result from the code in stdout, stderr, display_data, and error. | ||
You must to provide the code and the provided file path to run this tool. | ||
Your code should read the file from the sandbox path /home/user/{filename}. | ||
`, | ||
parameters: { | ||
type: "object", | ||
properties: { | ||
code: { | ||
type: "string", | ||
description: "The python code to execute in a single cell.", | ||
}, | ||
localFilePath: { | ||
type: "string", | ||
description: "The local file path to upload to the sandbox.", | ||
}, | ||
Comment on lines
+60
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
}, | ||
required: ["code"], | ||
}, | ||
|
@@ -88,11 +100,22 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> { | |
return this.codeInterpreter; | ||
} | ||
|
||
public async codeInterpret(code: string): Promise<InterpreterToolOutput> { | ||
public async codeInterpret( | ||
code: string, | ||
localFilePath: string, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes i also had that question for python, see https://github.com/run-llama/create-llama/pull/113/files#r1627342305 |
||
): Promise<InterpreterToolOutput> { | ||
const interpreter = await this.initInterpreter(); | ||
// Upload file to sandbox | ||
console.log(`Uploading file ${localFilePath} to sandbox`); | ||
const fileBuffer = fs.readFileSync(localFilePath); | ||
const fileName = path.basename(localFilePath); | ||
await interpreter.uploadFile(fileBuffer, fileName); | ||
console.log(`Uploaded file ${fileName} to sandbox`); | ||
|
||
// Execute code in sandbox | ||
console.log( | ||
`\n${"=".repeat(50)}\n> Running following AI-generated code:\n${code}\n${"=".repeat(50)}`, | ||
); | ||
const interpreter = await this.initInterpreter(); | ||
const exec = await interpreter.notebook.execCell(code); | ||
if (exec.error) console.error("[Code Interpreter error]", exec.error); | ||
const extraResult = await this.getExtraResult(exec.results[0]); | ||
|
@@ -105,7 +128,7 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> { | |
} | ||
|
||
async call(input: InterpreterParameter): Promise<InterpreterToolOutput> { | ||
const result = await this.codeInterpret(input.code); | ||
const result = await this.codeInterpret(input.code, input.localFilePath); | ||
await this.codeInterpreter?.close(); | ||
return result; | ||
} | ||
|
@@ -119,18 +142,26 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> { | |
try { | ||
const formats = res.formats(); // formats available for the result. Eg: ['png', ...] | ||
const base64DataArr = formats.map((f) => res[f as keyof Result]); // get base64 data for each format | ||
console.log("data", base64DataArr); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no output |
||
|
||
// save base64 data to file and return the url | ||
for (let i = 0; i < formats.length; i++) { | ||
const ext = formats[i]; | ||
const base64Data = base64DataArr[i]; | ||
if (ext && base64Data) { | ||
if (ext === "png" && base64Data) { | ||
const { filename } = this.saveToDisk(base64Data, ext); | ||
output.push({ | ||
type: ext as InterpreterExtraType, | ||
filename, | ||
url: this.getFileUrl(filename), | ||
}); | ||
} else { | ||
output.push({ | ||
type: ext as InterpreterExtraType, | ||
content: base64Data, | ||
filename: `output.${ext}`, | ||
url: "", | ||
}); | ||
} | ||
} | ||
} catch (error) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The e2b result is not only a file but also a raw text, html markdown text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@leehuwuj is that not independant of using files?