Files
langchain-sandbox/examples/codeact_agent.py
T

176 lines
4.6 KiB
Python

# pip install langgraph-codeact "langchain[anthropic]"
import asyncio
import inspect
import uuid
from typing import Any
from langchain.chat_models import init_chat_model
from langchain_sandbox import PyodideSandbox
from langgraph_codeact import EvalCoroutine, create_codeact
def create_pyodide_eval_fn(sandbox: PyodideSandbox) -> EvalCoroutine:
"""Create an eval_fn that uses PyodideSandbox.
"""
async def async_eval_fn(
code: str, _locals: dict[str, Any]
) -> tuple[str, dict[str, Any]]:
# Create a wrapper function that will execute the code and return locals
wrapper_code = f"""
def execute():
try:
# Execute the provided code
{chr(10).join(" " + line for line in code.strip().split(chr(10)))}
return locals()
except Exception as e:
return {{"error": str(e)}}
execute()
"""
# Convert functions in _locals to their string representation
context_setup = ""
for key, value in _locals.items():
if callable(value):
# Get the function's source code
src = inspect.getsource(value)
context_setup += f"\n{src}"
else:
context_setup += f"\n{key} = {repr(value)}"
try:
# Execute the code and get the result
response = await sandbox.execute(
code=context_setup + "\n\n" + wrapper_code,
)
# Check if execution was successful
if response.stderr:
return f"Error during execution: {response.stderr}", {}
# Get the output from stdout
output = (
response.stdout
if response.stdout
else "<Code ran, no output printed to stdout>"
)
result = response.result
# If there was an error in the result, return it
if isinstance(result, dict) and "error" in result:
return f"Error during execution: {result['error']}", {}
# Get the new variables by comparing with original locals
new_vars = {
k: v
for k, v in result.items()
if k not in _locals and not k.startswith("_")
}
return output, new_vars
except Exception as e:
return f"Error during PyodideSandbox execution: {repr(e)}", {}
return async_eval_fn
def add(a: float, b: float) -> float:
"""Add two numbers together."""
return a + b
def multiply(a: float, b: float) -> float:
"""Multiply two numbers together."""
return a * b
def divide(a: float, b: float) -> float:
"""Divide two numbers."""
return a / b
def subtract(a: float, b: float) -> float:
"""Subtract two numbers."""
return a - b
def sin(a: float) -> float:
"""Take the sine of a number."""
import math
return math.sin(a)
def cos(a: float) -> float:
"""Take the cosine of a number."""
import math
return math.cos(a)
def radians(a: float) -> float:
"""Convert degrees to radians."""
import math
return math.radians(a)
def exponentiation(a: float, b: float) -> float:
"""Raise one number to the power of another."""
return a**b
def sqrt(a: float) -> float:
"""Take the square root of a number."""
import math
return math.sqrt(a)
def ceil(a: float) -> float:
"""Round a number up to the nearest integer."""
import math
return math.ceil(a)
tools = [
add,
multiply,
divide,
subtract,
sin,
cos,
radians,
exponentiation,
sqrt,
ceil,
]
model = init_chat_model("claude-3-7-sonnet-latest", model_provider="anthropic")
sandbox = PyodideSandbox(allow_net=True)
eval_fn = create_pyodide_eval_fn(sandbox)
code_act = create_codeact(model, tools, eval_fn)
agent = code_act.compile()
query = """A batter hits a baseball at 45.847 m/s at an angle of 23.474° above the horizontal. The outfielder, who starts facing the batter, picks up the baseball as it lands, then throws it back towards the batter at 24.12 m/s at an angle of 39.12 degrees. How far is the baseball from where the batter originally hit it? Assume zero air resistance."""
async def run_agent(query: str):
# Stream agent outputs
async for typ, chunk in agent.astream(
{"messages": query},
stream_mode=["values", "messages"],
):
if typ == "messages":
print(chunk[0].content, end="")
elif typ == "values":
print("\n\n---answer---\n\n", chunk)
if __name__ == "__main__":
# Run the agent
asyncio.run(run_agent(query))