diff --git a/README.md b/README.md index 9ccefa5..48fe593 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ pip install langchain langchain-mcp-adapters langchain-anthropic ## Example +A full version of this in one file can be found [here](examples/math.py) + ### 1. Define your tools You can use any tools you want, including custom tools, LangChain tools, or MCP tools. In this example, we define a few simple math functions. @@ -117,7 +119,7 @@ import io def eval(code: str, _locals: dict) -> tuple: # Store original keys before execution original_keys = set(_locals.keys()) - + try: with contextlib.redirect_stdout(io.StringIO()) as f: exec(code, builtins.__dict__, _locals) @@ -126,7 +128,7 @@ def eval(code: str, _locals: dict) -> tuple: result = "" except Exception as e: result = f"Error during execution: {repr(e)}" - + # Determine new variables created during execution new_keys = set(_locals.keys()) - original_keys new_vars = {key: _locals[key] for key in new_keys} @@ -144,7 +146,8 @@ from langgraph.checkpoint.memory import MemorySaver model = init_chat_model("claude-3-7-sonnet-latest", model_provider="anthropic") -code_act = create_codeact(tools, model, eval, checkpointer=MemorySaver()) +code_act = create_codeact(model, tools, eval) +agent = code_act.compile(checkpointer=MemorySaver()) ``` ### 4. Run it! @@ -153,9 +156,14 @@ You can use the `.invoke()` method to get the final result, or the `.stream()` m ```py -for typ, chunk in code_act.stream( - "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.", +messages = [{ + "role": "user", + "content": "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." +}] +for typ, chunk in agent.stream( + {"messages": messages}, stream_mode=["values", "messages"], + config={"configurable": {"thread_id": 1}}, ): if typ == "messages": print(chunk[0].content, end="") diff --git a/examples/math_example.py b/examples/math_example.py new file mode 100644 index 0000000..3aca01b --- /dev/null +++ b/examples/math_example.py @@ -0,0 +1,113 @@ +import math + +from langchain_core.tools import tool +import builtins +import contextlib +import io + +from langchain.chat_models import init_chat_model +from langgraph_codeact import create_codeact +from langgraph.checkpoint.memory import MemorySaver + + +def eval(code: str, _locals: dict) -> tuple: + # Store original keys before execution + original_keys = set(_locals.keys()) + + try: + with contextlib.redirect_stdout(io.StringIO()) as f: + exec(code, builtins.__dict__, _locals) + result = f.getvalue() + if not result: + result = "" + except Exception as e: + result = f"Error during execution: {repr(e)}" + + # Determine new variables created during execution + new_keys = set(_locals.keys()) - original_keys + new_vars = {key: _locals[key] for key in new_keys} + return result, new_vars +@tool +def add(a: float, b: float) -> float: + """Add two numbers together.""" + return a + b + +@tool +def multiply(a: float, b: float) -> float: + """Multiply two numbers together.""" + return a * b + +@tool +def divide(a: float, b: float) -> float: + """Divide two numbers.""" + return a / b + +@tool +def subtract(a: float, b: float) -> float: + """Subtract two numbers.""" + return a - b + +@tool +def sin(a: float) -> float: + """Take the sine of a number.""" + return math.sin(a) + +@tool +def cos(a: float) -> float: + """Take the cosine of a number.""" + return math.cos(a) + +@tool +def radians(a: float) -> float: + """Convert degrees to radians.""" + return math.radians(a) + +@tool +def exponentiation(a: float, b: float) -> float: + """Raise one number to the power of another.""" + return a**b + +@tool +def sqrt(a: float) -> float: + """Take the square root of a number.""" + return math.sqrt(a) + +@tool +def ceil(a: float) -> float: + """Round a number up to the nearest integer.""" + 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") + +code_act = create_codeact(model, tools, eval) +agent = code_act.compile(checkpointer=MemorySaver()) + +if __name__ == "__main__": + messages = [{ + "role": "user", + "content": "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." + }] + for typ, chunk in agent.stream( + {"messages": messages}, + stream_mode=["values", "messages"], + config={"configurable": {"thread_id": 1}}, + ): + if typ == "messages": + print(chunk[0].content, end="") + elif typ == "values": + print("\n\n---answer---\n\n", chunk) + + diff --git a/langgraph_codeact/__init__.py b/langgraph_codeact/__init__.py index c1004da..de9f036 100644 --- a/langgraph_codeact/__init__.py +++ b/langgraph_codeact/__init__.py @@ -69,10 +69,11 @@ Reminder: use python code snippets to call tools""" def sandbox(state: CodeActState): script = state["script"] - context = state.get("context", {}) + old_context = state.get("context", {}) + context = {**old_context, **{tool.name: tool.func for tool in _tools}} # execute the script output, new_vars = eval_fn(script, context) - new_context = {**context, **new_vars} + new_context = {**old_context, **new_vars} return {"messages": [{"role": "user", "content": output}], "context": new_context} agent = StateGraph(CodeActState)