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 7 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
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}?`
);

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");
});
});
1 change: 1 addition & 0 deletions langchain/src/load/import_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,5 @@ export const optionalImportEntrypoints = [
"langchain/experimental/chat_models/anthropic_functions",
"langchain/experimental/llms/bittensor",
"langchain/experimental/hubs/makersuite/googlemakersuitehub",
"langchain/experimental/tools/pyinterpreter",
];
3 changes: 3 additions & 0 deletions langchain/src/load/import_type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,9 @@ export interface OptionalImportMap {
"langchain/experimental/hubs/makersuite/googlemakersuitehub"?:
| typeof import("../experimental/hubs/makersuite/googlemakersuitehub.js")
| Promise<typeof import("../experimental/hubs/makersuite/googlemakersuitehub.js")>;
"langchain/experimental/tools/pyinterpreter"?:
| typeof import("../experimental/tools/pyinterpreter.js")
| Promise<typeof import("../experimental/tools/pyinterpreter.js")>;
}

export interface SecretMap {
Expand Down
Loading