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

Added PyInterpreterTool #3090

Merged
merged 8 commits into from
Nov 28, 2023
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
1 change: 1 addition & 0 deletions docs/api_refs/typedoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@
"./langchain/src/experimental/llms/bittensor.ts",
"./langchain/src/experimental/hubs/makersuite/googlemakersuitehub.ts",
"./langchain/src/experimental/chains/violation_of_expectations/index.ts",
"./langchain/src/experimental/tools/pyinterpreter.ts",
"./langchain/src/evaluation/index.ts",
"./langchain/src/runnables/index.ts",
"./langchain/src/runnables/remote.ts"
Expand Down
20 changes: 20 additions & 0 deletions docs/core_docs/docs/integrations/tools/pyinterpreter.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
hide_table_of_contents: true
---

import CodeBlock from "@theme/CodeBlock";

# Python interpreter tool

:::warning
This tool executes code and can potentially perform destructive actions. Be careful that you trust any code passed to it!
:::

LangChain offers an experimental tool for executing arbitrary Python code.
This can be useful in combination with an LLM that can generate code to perform more powerful computations.

## Usage

import ToolExample from "@examples/tools/pyinterpreter.ts";

<CodeBlock language="typescript">{ToolExample}</CodeBlock>
24 changes: 24 additions & 0 deletions examples/src/tools/pyinterpreter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ChatPromptTemplate } from "langchain/prompts";
import { OpenAI } from "langchain/llms/openai";
import { PythonInterpreterTool } from "langchain/experimental/tools/pyinterpreter";
import { StringOutputParser } from "langchain/schema/output_parser";

const prompt = ChatPromptTemplate.fromTemplate(
`Generate python code that does {input}. Do not generate anything else.`
);

const model = new OpenAI({});

const interpreter = await PythonInterpreterTool.initialize({
indexURL: "../node_modules/pyodide",
});
const chain = prompt
.pipe(model)
.pipe(new StringOutputParser())
.pipe(interpreter);

const result = await chain.invoke({
input: `prints "Hello LangChain"`,
});

console.log(JSON.parse(result).stdout);
3 changes: 3 additions & 0 deletions langchain/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,9 @@ experimental/hubs/makersuite/googlemakersuitehub.d.ts
experimental/chains/violation_of_expectations.cjs
experimental/chains/violation_of_expectations.js
experimental/chains/violation_of_expectations.d.ts
experimental/tools/pyinterpreter.cjs
experimental/tools/pyinterpreter.js
experimental/tools/pyinterpreter.d.ts
evaluation.cjs
evaluation.js
evaluation.d.ts
Expand Down
13 changes: 13 additions & 0 deletions langchain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,9 @@
"experimental/chains/violation_of_expectations.cjs",
"experimental/chains/violation_of_expectations.js",
"experimental/chains/violation_of_expectations.d.ts",
"experimental/tools/pyinterpreter.cjs",
"experimental/tools/pyinterpreter.js",
"experimental/tools/pyinterpreter.d.ts",
"evaluation.cjs",
"evaluation.js",
"evaluation.d.ts",
Expand Down Expand Up @@ -960,6 +963,7 @@
"portkey-ai": "^0.1.11",
"prettier": "^2.8.3",
"puppeteer": "^19.7.2",
"pyodide": "^0.24.1",
"redis": "^4.6.6",
"release-it": "^15.10.1",
"replicate": "^0.18.0",
Expand Down Expand Up @@ -1065,6 +1069,7 @@
"playwright": "^1.32.1",
"portkey-ai": "^0.1.11",
"puppeteer": "^19.7.2",
"pyodide": "^0.24.1",
"redis": "^4.6.4",
"replicate": "^0.18.0",
"sonix-speech-recognition": "^2.1.1",
Expand Down Expand Up @@ -1333,6 +1338,9 @@
"puppeteer": {
"optional": true
},
"pyodide": {
"optional": true
},
"redis": {
"optional": true
},
Expand Down Expand Up @@ -2734,6 +2742,11 @@
"import": "./experimental/chains/violation_of_expectations.js",
"require": "./experimental/chains/violation_of_expectations.cjs"
},
"./experimental/tools/pyinterpreter": {
"types": "./experimental/tools/pyinterpreter.d.ts",
"import": "./experimental/tools/pyinterpreter.js",
"require": "./experimental/tools/pyinterpreter.cjs"
},
"./evaluation": {
"types": "./evaluation.d.ts",
"import": "./evaluation.js",
Expand Down
2 changes: 2 additions & 0 deletions langchain/scripts/create-entrypoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ const entrypoints = {
"experimental/hubs/makersuite/googlemakersuitehub",
"experimental/chains/violation_of_expectations":
"experimental/chains/violation_of_expectations/index",
"experimental/tools/pyinterpreter": "experimental/tools/pyinterpreter",
// evaluation
evaluation: "evaluation/index",
// runnables
Expand Down Expand Up @@ -498,6 +499,7 @@ const requiresOptionalDependency = [
"experimental/chat_models/anthropic_functions",
"experimental/llms/bittensor",
"experimental/hubs/makersuite/googlemakersuitehub",
"experimental/tools/pyinterpreter",
"util/convex",
];

Expand Down
239 changes: 239 additions & 0 deletions langchain/src/experimental/tools/pyinterpreter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import { loadPyodide, type PyodideInterface } from "pyodide";
import { Tool, ToolParams } from "../../tools/base.js";

export type PythonInterpreterToolParams = Parameters<typeof loadPyodide>[0] &
ToolParams & {
instance: PyodideInterface;
};

export class PythonInterpreterTool extends Tool {
static lc_name() {
return "PythonInterpreterTool";
}

name = "python_interpreter";

description = `Evaluates python code in a sandbox environment. The environment resets on every execution. You must send the whole script every time and print your outputs. Script should be pure python code that can be evaluated. Packages available:
${this.availableDefaultPackages}`;

pyodideInstance: PyodideInterface;

stdout = "";

stderr = "";

constructor(options: PythonInterpreterToolParams) {
super(options);
this.pyodideInstance = options.instance;
this.pyodideInstance.setStderr({
batched: (text: string) => {
this.stderr += text;
},
});

this.pyodideInstance.setStdout({
batched: (text: string) => {
this.stdout += text;
},
});
}

async addPackage(packageName: string) {
await this.pyodideInstance.loadPackage(packageName);
this.description += `, ${packageName}`;
}

get availableDefaultPackages(): string {
return [
"asciitree",
"astropy",
"atomicwrites",
"attrs",
"autograd",
"awkward-cpp",
"bcrypt",
"beautifulsoup4",
"biopython",
"bitarray",
"bitstring",
"bleach",
"bokeh",
"boost-histogram",
"brotli",
"cachetools",
"Cartopy",
"cbor-diag",
"certifi",
"cffi",
"cffi_example",
"cftime",
"click",
"cligj",
"cloudpickle",
"cmyt",
"colorspacious",
"contourpy",
"coolprop",
"coverage",
"cramjam",
"cryptography",
"cssselect",
"cycler",
"cytoolz",
"decorator",
"demes",
"deprecation",
"distlib",
"docutils",
"exceptiongroup",
"fastparquet",
"fiona",
"fonttools",
"freesasa",
"fsspec",
"future",
"galpy",
"gensim",
"geopandas",
"gmpy2",
"gsw",
"h5py",
"html5lib",
"idna",
"igraph",
"imageio",
"iniconfig",
"jedi",
"Jinja2",
"joblib",
"jsonschema",
"kiwisolver",
"lazy-object-proxy",
"lazy_loader",
"lightgbm",
"logbook",
"lxml",
"MarkupSafe",
"matplotlib",
"matplotlib-pyodide",
"micropip",
"mne",
"more-itertools",
"mpmath",
"msgpack",
"msprime",
"multidict",
"munch",
"mypy",
"netcdf4",
"networkx",
"newick",
"nlopt",
"nltk",
"nose",
"numcodecs",
"numpy",
"opencv-python",
"optlang",
"orjson",
"packaging",
"pandas",
"parso",
"patsy",
"peewee",
"Pillow",
"pillow_heif",
"pkgconfig",
"pluggy",
"protobuf",
"py",
"pyb2d",
"pyclipper",
"pycparser",
"pycryptodome",
"pydantic",
"pyerfa",
"Pygments",
"pyheif",
"pyinstrument",
"pynacl",
"pyodide-http",
"pyodide-tblib",
"pyparsing",
"pyproj",
"pyrsistent",
"pyshp",
"pytest",
"pytest-benchmark",
"python-dateutil",
"python-magic",
"python-sat",
"python_solvespace",
"pytz",
"pywavelets",
"pyxel",
"pyyaml",
"rebound",
"reboundx",
"regex",
"retrying",
"RobotRaconteur",
"ruamel.yaml",
"rust-panic-test",
"scikit-image",
"scikit-learn",
"scipy",
"screed",
"setuptools",
"shapely",
"simplejson",
"six",
"smart_open",
"soupsieve",
"sourmash",
"sparseqr",
"sqlalchemy",
"statsmodels",
"svgwrite",
"swiglpk",
"sympy",
"termcolor",
"texttable",
"threadpoolctl",
"tomli",
"tomli-w",
"toolz",
"tqdm",
"traits",
"tskit",
"typing-extensions",
"uncertainties",
"unyt",
"webencodings",
"wordcloud",
"wrapt",
"xarray",
"xgboost",
"xlrd",
"xyzservices",
"yarl",
"yt",
"zarr",
].join(", ");
}

static async initialize(
options: Omit<PythonInterpreterToolParams, "instance">
) {
const instance = await loadPyodide(options);
return new this({ ...options, instance });
}

async _call(script: string) {
this.stdout = "";
this.stderr = "";

await this.pyodideInstance.runPythonAsync(script);
return JSON.stringify({ stdout: this.stdout, stderr: this.stderr });
}
}
29 changes: 29 additions & 0 deletions langchain/src/experimental/tools/tests/pyinterpreter.int.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { test, expect } from "@jest/globals";
import { StringOutputParser } from "../../../schema/output_parser.js";
import { OpenAI } from "../../../llms/openai.js";
import { PromptTemplate } from "../../../prompts/index.js";
import { PythonInterpreterTool } from "../pyinterpreter.js";

describe("Python Interpreter testsuite", () => {
test("hello langchain", async () => {
const prompt = PromptTemplate.fromTemplate(
`Can you generate python code that: {input}? Do not generate anything else.`
);

const model = new OpenAI({});

const interpreter = await PythonInterpreterTool.initialize({
indexURL: "../node_modules/pyodide",
});
const chain = prompt
.pipe(model)
.pipe(new StringOutputParser())
.pipe(interpreter);

const result = await chain.invoke({
input: `prints "Hello LangChain"`,
});

expect(JSON.parse(result).stdout).toBe("Hello LangChain");
});
});
Loading