universal-mcp-agents 0.1.10__tar.gz → 0.1.11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of universal-mcp-agents might be problematic. Click here for more details.

Files changed (61) hide show
  1. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/GEMINI.md +5 -1
  2. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/PKG-INFO +1 -1
  3. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/bump_and_release.sh +1 -1
  4. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/pyproject.toml +1 -1
  5. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/codeact/agent.py +37 -59
  6. universal_mcp_agents-0.1.11/src/universal_mcp/agents/codeact/prompts.py +82 -0
  7. universal_mcp_agents-0.1.11/src/universal_mcp/agents/codeact/sandbox.py +39 -0
  8. universal_mcp_agents-0.1.11/src/universal_mcp/agents/codeact/state.py +9 -0
  9. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/uv.lock +2 -2
  10. universal_mcp_agents-0.1.10/src/universal_mcp/agents/codeact/prompts.py +0 -91
  11. universal_mcp_agents-0.1.10/src/universal_mcp/agents/codeact/sandbox.py +0 -51
  12. universal_mcp_agents-0.1.10/src/universal_mcp/agents/codeact/state.py +0 -10
  13. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/.gitignore +0 -0
  14. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/.pre-commit-config.yaml +0 -0
  15. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/PROMPTS.md +0 -0
  16. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/README.md +0 -0
  17. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/evals/__init__.py +0 -0
  18. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/evals/dataset.py +0 -0
  19. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/evals/datasets/exact.jsonl +0 -0
  20. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/evals/datasets/tasks.jsonl +0 -0
  21. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/evals/datasets/test.jsonl +0 -0
  22. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/evals/evaluators.py +0 -0
  23. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/evals/run.py +0 -0
  24. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/evals/utils.py +0 -0
  25. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/tests/test_agents.py +0 -0
  26. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/__init__.py +0 -0
  27. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/base.py +0 -0
  28. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtool2/__init__.py +0 -0
  29. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtool2/__main__.py +0 -0
  30. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtool2/agent.py +0 -0
  31. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtool2/graph.py +0 -0
  32. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtool2/meta_tools.py +0 -0
  33. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtool2/prompts.py +0 -0
  34. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtool2/state.py +0 -0
  35. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtoolcache/__init__.py +0 -0
  36. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtoolcache/__main__.py +0 -0
  37. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtoolcache/agent.py +0 -0
  38. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtoolcache/context.py +0 -0
  39. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtoolcache/graph.py +0 -0
  40. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtoolcache/prompts.py +0 -0
  41. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtoolcache/state.py +0 -0
  42. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/bigtoolcache/tools.py +0 -0
  43. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/builder.py +0 -0
  44. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/cli.py +0 -0
  45. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/codeact/__init__.py +0 -0
  46. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/codeact/__main__.py +0 -0
  47. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/codeact/utils.py +0 -0
  48. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/hil.py +0 -0
  49. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/llm.py +0 -0
  50. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/planner/__init__.py +0 -0
  51. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/planner/__main__.py +0 -0
  52. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/planner/graph.py +0 -0
  53. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/planner/prompts.py +0 -0
  54. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/planner/state.py +0 -0
  55. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/react.py +0 -0
  56. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/shared/prompts.py +0 -0
  57. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/shared/tool_node.py +0 -0
  58. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/simple.py +0 -0
  59. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/agents/utils.py +0 -0
  60. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/src/universal_mcp/applications/ui/app.py +0 -0
  61. {universal_mcp_agents-0.1.10 → universal_mcp_agents-0.1.11}/test.py +0 -0
@@ -43,4 +43,8 @@ Common commands (copy/paste)
43
43
  - Remove: `uv remove <pkg>`
44
44
  - Run app: `uv run python -m <your_module>` or `uv run main.py`
45
45
  - Tests: `uv run pytest -q`
46
- - Lint/format: `uv run ruff check .` and/or `uv run ruff format .`
46
+ - Lint/format: `uv run ruff check .` and/or `uv run ruff format .`
47
+
48
+
49
+
50
+ NEVER commit or push changes without asking explicitly.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp-agents
3
- Version: 0.1.10
3
+ Version: 0.1.11
4
4
  Summary: Add your description here
5
5
  Project-URL: Homepage, https://github.com/universal-mcp/applications
6
6
  Project-URL: Repository, https://github.com/universal-mcp/applications
@@ -9,7 +9,7 @@ uv sync --all-extras
9
9
 
10
10
  # Run tests with pytest
11
11
  echo "Running tests with pytest..."
12
- # uv run pytest
12
+ uv run pytest
13
13
 
14
14
  echo "Tests passed!"
15
15
 
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "universal-mcp-agents"
9
- version = "0.1.10"
9
+ version = "0.1.11"
10
10
  description = "Add your description here"
11
11
  readme = "README.md"
12
12
  authors = [
@@ -11,8 +11,6 @@ from universal_mcp.types import ToolConfig, ToolFormat
11
11
 
12
12
  from universal_mcp.agents.base import BaseAgent
13
13
  from universal_mcp.agents.codeact.prompts import (
14
- REFLECTION_PROMPT,
15
- RETRY_PROMPT,
16
14
  create_default_prompt,
17
15
  make_safe_function_name,
18
16
  )
@@ -39,11 +37,6 @@ class CodeActAgent(BaseAgent):
39
37
  self.tools_config = tools or {}
40
38
  self.registry = registry
41
39
  self.eval_fn = eval_unsafe
42
- self.reflection_prompt = REFLECTION_PROMPT
43
- self.reflection_model = self.model_instance
44
- self.max_reflections = 3
45
- self.tools_context = {}
46
- self.context = {}
47
40
  self.sandbox_timeout = sandbox_timeout
48
41
  self.processed_tools: list[StructuredTool | Callable] = []
49
42
 
@@ -57,13 +50,6 @@ class CodeActAgent(BaseAgent):
57
50
 
58
51
  self.instructions = create_default_prompt(self.processed_tools, self.instructions)
59
52
 
60
- for tool in self.processed_tools:
61
- safe_name = make_safe_function_name(tool.name)
62
- tool_callable = tool.coroutine if hasattr(tool, "coroutine") and tool.coroutine is not None else tool.func
63
- self.tools_context[safe_name] = tool_callable
64
-
65
- self.context = {**self.context, **self.tools_context}
66
-
67
53
  agent = StateGraph(CodeActState)
68
54
  agent.add_node("call_model", self.call_model)
69
55
  agent.add_node("sandbox", self.sandbox)
@@ -97,64 +83,56 @@ class CodeActAgent(BaseAgent):
97
83
  return content
98
84
 
99
85
  async def call_model(self, state: CodeActState) -> dict:
86
+ logger.debug(f"Calling model with state: {state}")
100
87
  model = self.model_instance
101
- reflection_model = self.reflection_model
102
88
 
103
- messages = [{"role": "system", "content": self.instructions}] + state["messages"]
89
+ # Find the last script and its output in the message history
90
+ previous_script = state.get("script", "")
91
+ sandbox_output = state.get("sandbox_output", "")
92
+
93
+ logger.debug(f"Previous script: {previous_script}")
94
+ logger.debug(f"Sandbox output: {sandbox_output}")
95
+
96
+ prompt_messages = [
97
+ {"role": "system", "content": self.instructions},
98
+ *state["messages"],
99
+ ]
100
+ if previous_script:
101
+ feedback_message = (
102
+ f"Here is the script you generated in the last turn:\n\n```python\n{previous_script}\n```\n\n"
103
+ )
104
+ if sandbox_output:
105
+ feedback_message += (
106
+ f"When executed, it produced the following output:\n\n```\n{sandbox_output}\n```\n\n"
107
+ )
108
+ feedback_message += "Based on this, please generate a new, improved script to continue the task. Remember to replace the old script entirely."
109
+ prompt_messages.append({"role": "user", "content": feedback_message})
110
+
111
+ logger.debug(f"Prompt messages: {prompt_messages}")
104
112
 
105
- response = await model.ainvoke(messages)
113
+ response = await model.ainvoke(prompt_messages)
114
+ logger.debug(f"Model response: {response}")
106
115
 
107
116
  text_content = self._extract_content(response)
108
117
  if not isinstance(text_content, str):
109
118
  raise ValueError(f"Content is not a string: {text_content}")
110
119
  code = extract_and_combine_codeblocks(text_content)
111
- logger.debug(f"Code: {code}")
112
-
113
- if self.max_reflections > 0 and code:
114
- reflection_count = 0
115
- while reflection_count < self.max_reflections:
116
- conversation_history = "\n".join(
117
- [
118
- f'<message role="{("user" if m.type == "human" else "assistant")}">\n{m.content}\n</message>'
119
- for m in state["messages"]
120
- ]
121
- )
122
- conversation_history += f'\n<message role="assistant">\n{response.content}\n</message>'
123
-
124
- formatted_prompt = REFLECTION_PROMPT.format(conversation_history=conversation_history)
125
-
126
- reflection_messages = [
127
- {"role": "system", "content": self.reflection_prompt},
128
- {"role": "user", "content": formatted_prompt},
129
- ]
130
- reflection_result = await reflection_model.ainvoke(reflection_messages)
131
-
132
- if "NONE" in reflection_result.content:
133
- break
134
-
135
- retry_prompt = RETRY_PROMPT.format(reflection_result=reflection_result.content)
136
-
137
- regeneration_messages = [
138
- {"role": "system", "content": self.instructions},
139
- *state["messages"],
140
- {"role": "assistant", "content": response.content},
141
- {"role": "user", "content": retry_prompt},
142
- ]
143
- response = await model.ainvoke(regeneration_messages)
144
-
145
- code = extract_and_combine_codeblocks(response.content)
146
-
147
- if not code:
148
- break
149
-
150
- reflection_count += 1
120
+ logger.debug(f"Extracted code: {code}")
151
121
 
152
122
  return {"messages": [response], "script": code}
153
123
 
154
124
  async def sandbox(self, state: CodeActState) -> dict:
155
- output, new_vars = await self.eval_fn(state["script"], self.context, timeout=self.sandbox_timeout)
156
- self.context = {**self.context, **new_vars}
125
+ logger.debug(f"Running sandbox with state: {state}")
126
+ tools_context = {}
127
+ for tool in self.processed_tools:
128
+ safe_name = make_safe_function_name(tool.name)
129
+ tool_callable = tool.coroutine if hasattr(tool, "coroutine") and tool.coroutine is not None else tool.func
130
+ tools_context[safe_name] = tool_callable
131
+
132
+ output, _ = await self.eval_fn(state["script"], tools_context, self.sandbox_timeout)
133
+ logger.debug(f"Sandbox output: {output}")
157
134
  return {
158
135
  "messages": [AIMessageChunk(content=output.strip())],
159
136
  "script": None,
137
+ "sandbox_output": output.strip(),
160
138
  }
@@ -0,0 +1,82 @@
1
+ import inspect
2
+ import re
3
+ from collections.abc import Sequence
4
+
5
+ from langchain_core.tools import StructuredTool
6
+
7
+
8
+ def make_safe_function_name(name: str) -> str:
9
+ """Convert a tool name to a valid Python function name."""
10
+ # Replace non-alphanumeric characters with underscores
11
+ safe_name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
12
+ # Ensure the name doesn't start with a digit
13
+ if safe_name and safe_name[0].isdigit():
14
+ safe_name = f"tool_{safe_name}"
15
+ # Handle empty name edge case
16
+ if not safe_name:
17
+ safe_name = "unnamed_tool"
18
+ return safe_name
19
+
20
+
21
+ def create_default_prompt(
22
+ tools: Sequence[StructuredTool],
23
+ base_prompt: str | None = None,
24
+ ):
25
+ """Create default prompt for the CodeAct agent."""
26
+ prompt = f"{base_prompt}\n\n" if base_prompt else ""
27
+ prompt += """You are a Python programmer. You will be given a task to perform.
28
+ Your goal is to write a self-contained Python script to accomplish the task.
29
+
30
+ In each turn, you will generate a complete Python script. The script will be executed in a fresh, stateless environment.
31
+ You will be given the previous script you generated and the output it produced.
32
+ Your task is to analyze the output to find errors or opportunities for improvement, and then generate a new, improved script.
33
+ You must take the previous script as a starting point and replace it with a new one that moves closer to the final solution.
34
+ Your final script must be a single, complete piece of code that can be executed independently.
35
+
36
+ The script must follow this structure:
37
+ 1. All necessary imports at the top.
38
+ 2. An `async def main():` function containing the core logic.
39
+ 3. Do NOT include any code outside of the `async def main()` function, and do NOT call it. The execution environment handles this.
40
+
41
+ Any output you want to see from the code should be printed to the console from within the `main` function.
42
+ Code should be output in a fenced code block (e.g. ```python ... ```).
43
+
44
+ If you need to ask for more information or provide the final answer, you can output text to be shown directly to the user.
45
+
46
+ In addition to the Python Standard Library, you can use the following functions:"""
47
+
48
+ for tool in tools:
49
+ # Use coroutine if it exists, otherwise use func
50
+ tool_callable = tool.coroutine if hasattr(tool, "coroutine") and tool.coroutine is not None else tool.func
51
+ # Create a safe function name
52
+ safe_name = make_safe_function_name(tool.name)
53
+ # Determine if it's an async function
54
+ is_async = inspect.iscoroutinefunction(tool_callable)
55
+ # Add appropriate function definition
56
+ prompt += f'''\n{"async " if is_async else ""}def {safe_name}{str(inspect.signature(tool_callable))}:
57
+ """{tool.description}"""
58
+ ...
59
+ '''
60
+
61
+ prompt += """\n\n\nAlways use print() statements to explore data structures and function outputs. Simply returning values will not display them back to you for inspection. For example, use print(result) instead of just 'result'.
62
+
63
+ As you don't know the output schema of the additional Python functions you have access to, start from exploring their contents before building a final solution.
64
+
65
+ IMPORTANT CODING STRATEGY:
66
+ 1. All your code must be inside an `async def main()` function.
67
+ 2. Do NOT import `asyncio` or call `main()`. The execution environment handles this.
68
+ 3. Since many of the provided tools are async, you must use `await` to call them from within `main()`.
69
+ 4. Write code up to the point where you make an API call/tool usage with an output.
70
+ 5. Print the type/shape and a sample entry of this output, and using that knowledge proceed to write the further code.
71
+
72
+ This means:
73
+ - Write code that makes the API call or tool usage
74
+ - Print the result with type information: print(f"Type: {type(result)}")
75
+ - Print the shape/structure: print(f"Shape/Keys: {result.keys() if isinstance(result, dict) else len(result) if isinstance(result, (list, tuple)) else 'N/A'}")
76
+ - Print a sample entry: print(f"Sample: {result[0] if isinstance(result, (list, tuple)) and len(result) > 0 else result}")
77
+ - Then, based on this knowledge, write the code to process/use this data
78
+
79
+ Reminder: use Python code snippets to call tools
80
+
81
+ When you have completely finished the task, present the final result from your script to the user in a clean and readable Markdown format. Do not just summarize what you did; provide the actual output. For example, if you were asked to find unsubscribe links and your script found them, your final response should be a Markdown-formatted list of those links. After you have provided the final output, you MUST end your response with the exact phrase "TASK_COMPLETE"."""
82
+ return prompt
@@ -0,0 +1,39 @@
1
+ import asyncio
2
+ import builtins
3
+ import contextlib
4
+ import io
5
+ from typing import Any
6
+
7
+ from loguru import logger
8
+
9
+
10
+ async def eval_unsafe(code: str, _locals: dict[str, Any], timeout: int = 10) -> tuple[str, dict[str, Any]]:
11
+ """Executes a string of Python code in a sandboxed environment."""
12
+ # Store original keys before execution
13
+ original_keys = set(_locals.keys())
14
+ result = f"Executing code...\n{code}\n\nOutput:\n"
15
+ result += "=" * 50 + "\n"
16
+ try:
17
+ logger.debug(f"Executing code with timeout {timeout}")
18
+ with contextlib.redirect_stdout(io.StringIO()) as f:
19
+ # Execute the code in the provided locals context
20
+ # This should define an async function `main`
21
+ exec(code, builtins.__dict__, _locals)
22
+
23
+ if "main" in _locals and asyncio.iscoroutinefunction(_locals["main"]):
24
+ # Run the main async function
25
+ await asyncio.wait_for(_locals["main"](), timeout=timeout)
26
+ else:
27
+ result += "\nError: No `async def main()` function found in the script."
28
+
29
+ output = f.getvalue()
30
+ result += output
31
+ if not output:
32
+ result += "<code ran, no output printed to stdout>"
33
+ except Exception as e:
34
+ result += f"Error during execution: {repr(e)}"
35
+
36
+ # Determine new variables created during execution
37
+ new_keys = set(_locals.keys()) - original_keys
38
+ new_vars = {key: _locals[key] for key in new_keys}
39
+ return result, new_vars
@@ -0,0 +1,9 @@
1
+ from langgraph.graph import MessagesState
2
+ from pydantic import Field
3
+
4
+
5
+ class CodeActState(MessagesState):
6
+ """State for CodeAct agent."""
7
+
8
+ script: str | None = Field(default=None, description="The Python code script to be executed.")
9
+ sandbox_output: str | None = Field(default=None, description="The output of the Python code script execution.")
@@ -1574,7 +1574,7 @@ name = "importlib-metadata"
1574
1574
  version = "8.7.0"
1575
1575
  source = { registry = "https://pypi.org/simple" }
1576
1576
  dependencies = [
1577
- { name = "zipp", marker = "python_full_version < '3.13'" },
1577
+ { name = "zipp", marker = "python_full_version < '3.12'" },
1578
1578
  ]
1579
1579
  sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" }
1580
1580
  wheels = [
@@ -4389,7 +4389,7 @@ wheels = [
4389
4389
 
4390
4390
  [[package]]
4391
4391
  name = "universal-mcp-agents"
4392
- version = "0.1.9"
4392
+ version = "0.1.10"
4393
4393
  source = { editable = "." }
4394
4394
  dependencies = [
4395
4395
  { name = "langchain-anthropic" },
@@ -1,91 +0,0 @@
1
- import inspect
2
- import re
3
- from collections.abc import Sequence
4
-
5
- from langchain_core.tools import StructuredTool
6
-
7
-
8
- def make_safe_function_name(name: str) -> str:
9
- """Convert a tool name to a valid Python function name."""
10
- # Replace non-alphanumeric characters with underscores
11
- safe_name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
12
- # Ensure the name doesn't start with a digit
13
- if safe_name and safe_name[0].isdigit():
14
- safe_name = f"tool_{safe_name}"
15
- # Handle empty name edge case
16
- if not safe_name:
17
- safe_name = "unnamed_tool"
18
- return safe_name
19
-
20
-
21
- def create_default_prompt(
22
- tools: Sequence[StructuredTool],
23
- base_prompt: str | None = None,
24
- ):
25
- """Create default prompt for the CodeAct agent."""
26
- prompt = f"{base_prompt}\n\n" if base_prompt else ""
27
- prompt += """You will be given a task to perform. You should output either
28
- - a Python code snippet that provides the solution to the task, or a step towards the solution. Any output you want to extract from the code should be printed to the console. Code should be output in a fenced code block.
29
- - text to be shown directly to the user, if you want to ask for more information or provide the final answer.
30
-
31
- In addition to the Python Standard Library, you can use the following functions:"""
32
-
33
- for tool in tools:
34
- # Use coroutine if it exists, otherwise use func
35
- tool_callable = tool.coroutine if hasattr(tool, "coroutine") and tool.coroutine is not None else tool.func
36
- # Create a safe function name
37
- safe_name = make_safe_function_name(tool.name)
38
- # Determine if it's an async function
39
- is_async = inspect.iscoroutinefunction(tool_callable)
40
- # Add appropriate function definition
41
- prompt += f'''\n{"async " if is_async else ""}def {safe_name}{str(inspect.signature(tool_callable))}:
42
- """{tool.description}"""
43
- ...
44
- '''
45
-
46
- prompt += """
47
-
48
- Variables defined at the top level of previous code snippets can be referenced in your code.
49
-
50
- Always use print() statements to explore data structures and function outputs. Simply returning values will not display them back to you for inspection. For example, use print(result) instead of just 'result'.
51
-
52
- As you don't know the output schema of the additional Python functions you have access to, start from exploring their contents before building a final solution.
53
-
54
- IMPORTANT CODING STRATEGY:
55
- 1. Only write code up to the point where you make an API call/tool usage with an output
56
- 2. Print the type/shape and a sample entry of this output, and using that knowledge proceed to write the further code
57
-
58
- This means:
59
- - Write code that makes the API call or tool usage
60
- - Print the result with type information: print(f"Type: {type(result)}")
61
- - Print the shape/structure: print(f"Shape/Keys: {result.keys() if isinstance(result, dict) else len(result) if isinstance(result, (list, tuple)) else 'N/A'}")
62
- - Print a sample entry: print(f"Sample: {result[0] if isinstance(result, (list, tuple)) and len(result) > 0 else result}")
63
- - Then, based on this knowledge, write the code to process/use this data
64
-
65
- Reminder: use Python code snippets to call tools
66
-
67
- When you have completely finished the task and provided the final answer, you MUST end your response with the exact phrase "TASK_COMPLETE".
68
- """
69
- return prompt
70
-
71
-
72
- REFLECTION_PROMPT = """
73
- Review the assistant's latest code for as per the quality rules:
74
-
75
- <conversation_history>
76
- {conversation_history}
77
- </conversation_history>
78
-
79
- If you find ANY of these issues, describe the problem briefly and clearly.
80
- If NO issues are found, respond with EXACTLY: "NONE"
81
- """
82
-
83
- RETRY_PROMPT = """
84
- I need you to completely regenerate your previous response based on this feedback:
85
-
86
- '''
87
- {reflection_result}
88
- '''
89
-
90
- DO NOT reference the feedback directly. Instead, provide a completely new response that addresses the issues.
91
- """
@@ -1,51 +0,0 @@
1
- import asyncio
2
- import builtins
3
- import contextlib
4
- import io
5
- from typing import Any
6
-
7
-
8
- async def eval_unsafe(code: str, _locals: dict[str, Any], timeout: int = 10) -> tuple[str, dict[str, Any]]:
9
- """
10
- Execute code in a non-blocking way and return the output and changed variables.
11
- """
12
- result = f"Executing code...\n{code}\n\nOutput:\n"
13
- result += "=" * 50 + "\n"
14
-
15
- # Create a combined globals/locals environment that includes builtins
16
- # and the provided context. This allows nested functions to access tools.
17
- execution_env = {**builtins.__dict__, **_locals}
18
-
19
- def sync_eval_in_thread():
20
- """Synchronously execute code and capture output."""
21
- try:
22
- with contextlib.redirect_stdout(io.StringIO()) as f:
23
- exec(code, execution_env)
24
- output = f.getvalue()
25
- if not output:
26
- output = "<code ran, no output printed to stdout>"
27
- return output
28
- except Exception as e:
29
- return f"Error during execution: {repr(e)}"
30
-
31
- # Run the synchronous exec in a separate thread to avoid blocking the event loop.
32
- try:
33
- output = await asyncio.wait_for(asyncio.to_thread(sync_eval_in_thread), timeout=timeout)
34
- except asyncio.TimeoutError:
35
- output = f"Error: Code execution timed out after {timeout} seconds."
36
- result += output
37
-
38
- # Identify all variables that are not part of the original builtins
39
- # and were not in the initial _locals, or were changed.
40
- changed_vars = {}
41
- builtin_keys = set(builtins.__dict__.keys())
42
-
43
- for key, value in execution_env.items():
44
- if key in builtin_keys:
45
- continue # Skip builtins
46
-
47
- # Check if the key is new or if the value has changed
48
- if key not in _locals or _locals[key] is not value:
49
- changed_vars[key] = value
50
-
51
- return result, changed_vars
@@ -1,10 +0,0 @@
1
- from typing import Any
2
-
3
- from langgraph.graph import MessagesState
4
-
5
-
6
- class CodeActState(MessagesState):
7
- """State for CodeAct agent."""
8
-
9
- script: str | None
10
- """The Python code script to be executed."""