Support for requests using langchain-sandbox as eval function #14

Open
opened 2026-02-16 07:16:31 -05:00 by yindo · 3 comments
Owner

Originally created by @valenvivaldi on GitHub (May 12, 2025).

Hi, everyone!
First of all, I want to thank you for your amazing work on LangChain and LangGraph.
I am not very sure if the issue need to be reported here or langchain-sandbox repo, but I will try to explain the issue I am facing.

I’m building a CodeAct agent using PyodideSandbox example added recently in langchain-sandbox and this repo, and encountered consistent issues when trying to make network requests from within the sandboxed environment. I tried three approaches, but none work reliably.

I will share the code below, which each approach I tried, and the errors I encountered.


import asyncio
import inspect
from typing import Any

from langchain.chat_models import init_chat_model
from langchain_sandbox import PyodideSandbox
from langgraph.checkpoint.memory import MemorySaver

from langgraph_codeact import EvalCoroutine, create_codeact


def create_pyodide_eval_fn(sandbox_dir: str = "./sessions", session_id: str | None = None) -> EvalCoroutine:
    """Create an eval_fn that uses PyodideSandbox.

    Args:
        sandbox_dir: Directory to store session files
        session_id: ID of the session to use

    Returns:
        A function that evaluates code using PyodideSandbox
    """
    sandbox = PyodideSandbox(sandbox_dir, allow_net=True)

    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,
                session_id=session_id,
            )

            # 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("gpt-4.1")

eval_fn = create_pyodide_eval_fn()
code_act = create_codeact(model, tools, eval_fn)
agent = code_act.compile(checkpointer=MemorySaver())


async def run_agent(query: str, thread_id: str):
    config = {"configurable": {"thread_id": thread_id}}
    # Stream agent outputs
    async for typ, chunk in agent.astream(
        {"messages": query},
        stream_mode=["values", "messages"],
        config=config,
    ):
        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("""Check if google is reachable doing a GET request to https://www.google.com""", "1")
    )

    ## Run the agent, telling to use from js import fetch, JSON

    asyncio.run(
        run_agent(
            """Check if google is reachable doing a GET request to https://www.google.com, using js API
            Example:
            import json
            from js import fetch, JSON
            
            async def get_data():
                response = await fetch('https://mocki.io/v1/d4867d8b-b5d5-4a48-a4ab-79131b5809b8',{'method':'GET'})
                data = await response.json()
                print(JSON.stringify(data))
            
            await get_data()
             
             """,
            "2",
        )
        ### Error in "sandbox" node in Langsmith:
    )
    """
            Error during execution: Traceback (most recent call last):
              File "/lib/python312.zip/_pyodide/_base.py", line 606, in eval_code_async
                .compile()
                 ^^^^^^^^^
              File "/lib/python312.zip/_pyodide/_base.py", line 297, in compile
                self._gen.send(self.ast)
              File "/lib/python312.zip/_pyodide/_base.py", line 166, in _parse_and_compile_gen
                return compile(mod, filename, mode, flags, dont_inherit, optimize)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
              File "<exec>", line 67
            SyntaxError: 'await' outside async function
            """
    ## Run the agent, telling to use pyodide-http to patch the requests
    asyncio.run(
        run_agent(
            """Check if google is reachable doing a GET request to https://www.google.com, using pyodide-http
            Example:
            import pyodide_http 
            pyodide_http.patch_all()
            import json
            import requests
            
            
            def get_data():
                response = requests.get('https://mocki.io/v1/d4867d8b-b5d5-4a48-a4ab-79131b5809b8')
                data = response.json()
                print(json.dumps(data))
             """,
            "3",
        )
    )
    ## Error in "sandbox" node in Langsmith:
    """
          An error occurred: ('Connection aborted.', HTTPException('urllib3 only works in Node.js with pyodide.runPythonAsync and requires the flag --experimental-wasm-stack-switching in  versions of node <24.'))
  """

Is there a way to allow network requests in the sandboxed environment? Or its there a workaround to achieve this?
Or its simply not supported yet?

Thanks for your help!

Originally created by @valenvivaldi on GitHub (May 12, 2025). Hi, everyone! First of all, I want to thank you for your amazing work on LangChain and LangGraph. I am not very sure if the issue need to be reported here or `langchain-sandbox` repo, but I will try to explain the issue I am facing. I’m building a CodeAct agent using PyodideSandbox example added recently in `langchain-sandbox` and this repo, and encountered consistent issues when trying to make network requests from within the sandboxed environment. I tried three approaches, but none work reliably. I will share the code below, which each approach I tried, and the errors I encountered. ``` python import asyncio import inspect from typing import Any from langchain.chat_models import init_chat_model from langchain_sandbox import PyodideSandbox from langgraph.checkpoint.memory import MemorySaver from langgraph_codeact import EvalCoroutine, create_codeact def create_pyodide_eval_fn(sandbox_dir: str = "./sessions", session_id: str | None = None) -> EvalCoroutine: """Create an eval_fn that uses PyodideSandbox. Args: sandbox_dir: Directory to store session files session_id: ID of the session to use Returns: A function that evaluates code using PyodideSandbox """ sandbox = PyodideSandbox(sandbox_dir, allow_net=True) 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, session_id=session_id, ) # 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("gpt-4.1") eval_fn = create_pyodide_eval_fn() code_act = create_codeact(model, tools, eval_fn) agent = code_act.compile(checkpointer=MemorySaver()) async def run_agent(query: str, thread_id: str): config = {"configurable": {"thread_id": thread_id}} # Stream agent outputs async for typ, chunk in agent.astream( {"messages": query}, stream_mode=["values", "messages"], config=config, ): 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("""Check if google is reachable doing a GET request to https://www.google.com""", "1") ) ## Run the agent, telling to use from js import fetch, JSON asyncio.run( run_agent( """Check if google is reachable doing a GET request to https://www.google.com, using js API Example: import json from js import fetch, JSON async def get_data(): response = await fetch('https://mocki.io/v1/d4867d8b-b5d5-4a48-a4ab-79131b5809b8',{'method':'GET'}) data = await response.json() print(JSON.stringify(data)) await get_data() """, "2", ) ### Error in "sandbox" node in Langsmith: ) """ Error during execution: Traceback (most recent call last): File "/lib/python312.zip/_pyodide/_base.py", line 606, in eval_code_async .compile() ^^^^^^^^^ File "/lib/python312.zip/_pyodide/_base.py", line 297, in compile self._gen.send(self.ast) File "/lib/python312.zip/_pyodide/_base.py", line 166, in _parse_and_compile_gen return compile(mod, filename, mode, flags, dont_inherit, optimize) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "<exec>", line 67 SyntaxError: 'await' outside async function """ ## Run the agent, telling to use pyodide-http to patch the requests asyncio.run( run_agent( """Check if google is reachable doing a GET request to https://www.google.com, using pyodide-http Example: import pyodide_http pyodide_http.patch_all() import json import requests def get_data(): response = requests.get('https://mocki.io/v1/d4867d8b-b5d5-4a48-a4ab-79131b5809b8') data = response.json() print(json.dumps(data)) """, "3", ) ) ## Error in "sandbox" node in Langsmith: """ An error occurred: ('Connection aborted.', HTTPException('urllib3 only works in Node.js with pyodide.runPythonAsync and requires the flag --experimental-wasm-stack-switching in versions of node <24.')) """ ``` Is there a way to allow network requests in the sandboxed environment? Or its there a workaround to achieve this? Or its simply not supported yet? Thanks for your help!
Author
Owner

@vbarda commented on GitHub (May 15, 2025):

I believe only httpx requests are supported at the moment cc @eyurtsev

@vbarda commented on GitHub (May 15, 2025): I believe only `httpx` requests are supported at the moment cc @eyurtsev
Author
Owner

@eyurtsev commented on GitHub (May 15, 2025):

Please use async httpx for making network requests.

functionality of requests is limited by pyodide

@eyurtsev commented on GitHub (May 15, 2025): Please use async httpx for making network requests. functionality of `requests` is limited by pyodide
Author
Owner

@valenvivaldi commented on GitHub (Jun 4, 2025):

Please use async httpx for making network requests.

functionality of requests is limited by pyodide

Okay, I think I tried that but it didn't work. I'll try again, by chance you don't have any sample code to give as a “template” so that I know how to generate the code correctly?

@valenvivaldi commented on GitHub (Jun 4, 2025): > Please use async httpx for making network requests. > > functionality of `requests` is limited by pyodide Okay, I think I tried that but it didn't work. I'll try again, by chance you don't have any sample code to give as a “template” so that I know how to generate the code correctly?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph-codeact#14