universal-mcp-agents 0.1.19rc1__py3-none-any.whl → 0.1.24rc3__py3-none-any.whl
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.
- universal_mcp/agents/__init__.py +15 -16
- universal_mcp/agents/base.py +46 -35
- universal_mcp/agents/bigtool/state.py +1 -1
- universal_mcp/agents/cli.py +2 -5
- universal_mcp/agents/codeact0/__init__.py +2 -3
- universal_mcp/agents/codeact0/__main__.py +4 -7
- universal_mcp/agents/codeact0/agent.py +444 -96
- universal_mcp/agents/codeact0/langgraph_agent.py +1 -1
- universal_mcp/agents/codeact0/llm_tool.py +2 -254
- universal_mcp/agents/codeact0/prompts.py +247 -137
- universal_mcp/agents/codeact0/sandbox.py +52 -18
- universal_mcp/agents/codeact0/state.py +26 -6
- universal_mcp/agents/codeact0/tools.py +400 -74
- universal_mcp/agents/codeact0/utils.py +175 -11
- universal_mcp/agents/codeact00/__init__.py +3 -0
- universal_mcp/agents/{unified → codeact00}/__main__.py +4 -6
- universal_mcp/agents/codeact00/agent.py +578 -0
- universal_mcp/agents/codeact00/config.py +77 -0
- universal_mcp/agents/{unified → codeact00}/langgraph_agent.py +2 -2
- universal_mcp/agents/{unified → codeact00}/llm_tool.py +1 -1
- universal_mcp/agents/codeact00/prompts.py +364 -0
- universal_mcp/agents/{unified → codeact00}/sandbox.py +52 -18
- universal_mcp/agents/codeact00/state.py +66 -0
- universal_mcp/agents/codeact00/tools.py +525 -0
- universal_mcp/agents/codeact00/utils.py +678 -0
- universal_mcp/agents/codeact01/__init__.py +3 -0
- universal_mcp/agents/{codeact → codeact01}/__main__.py +4 -11
- universal_mcp/agents/codeact01/agent.py +413 -0
- universal_mcp/agents/codeact01/config.py +77 -0
- universal_mcp/agents/codeact01/langgraph_agent.py +14 -0
- universal_mcp/agents/codeact01/llm_tool.py +25 -0
- universal_mcp/agents/codeact01/prompts.py +246 -0
- universal_mcp/agents/codeact01/sandbox.py +162 -0
- universal_mcp/agents/{unified → codeact01}/state.py +26 -10
- universal_mcp/agents/codeact01/tools.py +648 -0
- universal_mcp/agents/{unified → codeact01}/utils.py +175 -11
- universal_mcp/agents/llm.py +14 -4
- universal_mcp/agents/react.py +3 -3
- universal_mcp/agents/sandbox.py +124 -69
- universal_mcp/applications/llm/app.py +76 -24
- {universal_mcp_agents-0.1.19rc1.dist-info → universal_mcp_agents-0.1.24rc3.dist-info}/METADATA +6 -5
- universal_mcp_agents-0.1.24rc3.dist-info/RECORD +66 -0
- universal_mcp/agents/codeact/__init__.py +0 -3
- universal_mcp/agents/codeact/agent.py +0 -240
- universal_mcp/agents/codeact/models.py +0 -11
- universal_mcp/agents/codeact/prompts.py +0 -82
- universal_mcp/agents/codeact/sandbox.py +0 -85
- universal_mcp/agents/codeact/state.py +0 -11
- universal_mcp/agents/codeact/utils.py +0 -68
- universal_mcp/agents/codeact0/playbook_agent.py +0 -355
- universal_mcp/agents/unified/README.md +0 -45
- universal_mcp/agents/unified/__init__.py +0 -3
- universal_mcp/agents/unified/agent.py +0 -289
- universal_mcp/agents/unified/prompts.py +0 -192
- universal_mcp/agents/unified/tools.py +0 -188
- universal_mcp_agents-0.1.19rc1.dist-info/RECORD +0 -64
- {universal_mcp_agents-0.1.19rc1.dist-info → universal_mcp_agents-0.1.24rc3.dist-info}/WHEEL +0 -0
{universal_mcp_agents-0.1.19rc1.dist-info → universal_mcp_agents-0.1.24rc3.dist-info}/METADATA
RENAMED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: universal-mcp-agents
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.24rc3
|
|
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
|
|
7
7
|
Author-email: Manoj Bajaj <manojbajaj95@gmail.com>
|
|
8
8
|
License: MIT
|
|
9
9
|
Requires-Python: >=3.11
|
|
10
|
+
Requires-Dist: cloudpickle>=3.1.1
|
|
10
11
|
Requires-Dist: langchain-anthropic>=0.3.19
|
|
11
12
|
Requires-Dist: langchain-google-genai>=2.1.10
|
|
12
13
|
Requires-Dist: langchain-openai>=0.3.32
|
|
13
14
|
Requires-Dist: langgraph>=0.6.6
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist: universal-mcp
|
|
16
|
-
Requires-Dist: universal-mcp>=0.1.24rc25
|
|
15
|
+
Requires-Dist: universal-mcp-applications>=0.1.30
|
|
16
|
+
Requires-Dist: universal-mcp>=0.1.24rc29
|
|
17
17
|
Provides-Extra: dev
|
|
18
18
|
Requires-Dist: pre-commit; extra == 'dev'
|
|
19
19
|
Requires-Dist: ruff; extra == 'dev'
|
|
20
|
+
Requires-Dist: typer>=0.17.4; extra == 'dev'
|
|
20
21
|
Provides-Extra: test
|
|
21
|
-
Requires-Dist: pytest-asyncio>=1.
|
|
22
|
+
Requires-Dist: pytest-asyncio>=1.2.0; extra == 'test'
|
|
22
23
|
Requires-Dist: pytest-cov; extra == 'test'
|
|
23
24
|
Requires-Dist: pytest<9.0.0,>=7.0.0; extra == 'test'
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
universal_mcp/agents/__init__.py,sha256=j2kIa1oM1gg_8dDdLO18MhqV6UpsSugU5Q8VfLAkVG4,1344
|
|
2
|
+
universal_mcp/agents/base.py,sha256=eJjGHp1jAKKs_B-o5AoXqxN_MJzWQYgZV_5PMACyNEo,7749
|
|
3
|
+
universal_mcp/agents/cli.py,sha256=d3O5BxkT4axHcKnL7gM1SvneWoZMh1QQoElk03KeuU4,950
|
|
4
|
+
universal_mcp/agents/hil.py,sha256=_5PCK6q0goGm8qylJq44aSp2MadP-yCPvhOJYKqWLMo,3808
|
|
5
|
+
universal_mcp/agents/llm.py,sha256=Fgmru1eAIsykT6aBRJd2_TYYf6kO2OwD8onIdR1goU4,2058
|
|
6
|
+
universal_mcp/agents/react.py,sha256=ocYm94HOiJVI2zwTjO1K2PNfVY7EILLJ6cd__jnGHPs,3327
|
|
7
|
+
universal_mcp/agents/sandbox.py,sha256=YxTGp_zsajuN7FUn0Q4PFjuXczgLht7oKql_gyb2Gf4,5112
|
|
8
|
+
universal_mcp/agents/simple.py,sha256=NSATg5TWzsRNS7V3LFiDG28WSOCIwCdcC1g7NRwg2nM,2095
|
|
9
|
+
universal_mcp/agents/utils.py,sha256=P6W9k6XAOBp6tdjC2VTP4tE0B2M4-b1EDmr-ylJ47Pw,7765
|
|
10
|
+
universal_mcp/agents/bigtool/__init__.py,sha256=mZG8dsaCVyKlm82otxtiTA225GIFLUCUUYPEIPF24uw,2299
|
|
11
|
+
universal_mcp/agents/bigtool/__main__.py,sha256=0i-fbd2yQ90qa8n2nM3luqoJVN9Reh5HZXR5oK7SAck,445
|
|
12
|
+
universal_mcp/agents/bigtool/agent.py,sha256=mtCDNN8WjE2hjJjooDqusmbferKBHeJMHrhXUPUWaVc,252
|
|
13
|
+
universal_mcp/agents/bigtool/context.py,sha256=ny7gd-vvVpUOYAeQbAEUT0A6Vm6Nn2qGywxTzPBzYFg,929
|
|
14
|
+
universal_mcp/agents/bigtool/graph.py,sha256=2Sy0dtevTWeT3hJDq4BDerZFvk_zJqx15j8VH2XLq8Y,5848
|
|
15
|
+
universal_mcp/agents/bigtool/prompts.py,sha256=Joi5mCzZX63aM_6eBrMOKuNRHjTkceVIibSsGBGqhYE,2041
|
|
16
|
+
universal_mcp/agents/bigtool/state.py,sha256=Voh7HXGC0PVe_0qoRZ8ZYg9akg65_2jQIAV2eIwperE,737
|
|
17
|
+
universal_mcp/agents/bigtool/tools.py,sha256=-u80ta6xEaqzEMSzDVe3QZiTZm3YlgLkBD8WTghzClw,6315
|
|
18
|
+
universal_mcp/agents/builder/__main__.py,sha256=VJDJOr-dJJerT53ibh5LVqIsMJ0m0sG2UlzFB784pKw,11680
|
|
19
|
+
universal_mcp/agents/builder/builder.py,sha256=mh3MZpMVB1FE1DWzvMW9NnfiaF145VGn8cJzKSYUlzY,8587
|
|
20
|
+
universal_mcp/agents/builder/helper.py,sha256=8igR1b3Gy_N2u3WxHYKIWzvw7F5BMnfpO2IU74v6vsw,2680
|
|
21
|
+
universal_mcp/agents/builder/prompts.py,sha256=8Xs6uzTUHguDRngVMLak3lkXFkk2VV_uQXaDllzP5cI,4670
|
|
22
|
+
universal_mcp/agents/builder/state.py,sha256=7DeWllxfN-yD6cd9wJ3KIgjO8TctkJvVjAbZT8W_zqk,922
|
|
23
|
+
universal_mcp/agents/codeact0/__init__.py,sha256=8-fvUo1Sm6dURGI-lW-X3Kd78LqySYbb5NMkNJ4NDwg,76
|
|
24
|
+
universal_mcp/agents/codeact0/__main__.py,sha256=YyIoecUcKVUhTcCACzLlSmYrayMDsdwzDEqaV4VV4CE,766
|
|
25
|
+
universal_mcp/agents/codeact0/agent.py,sha256=3cFnQMxj-Bi-twM7sgGNfUp_ui7nb8iI-7_Hl_C_wHM,23745
|
|
26
|
+
universal_mcp/agents/codeact0/config.py,sha256=H-1woj_nhSDwf15F63WYn723y4qlRefXzGxuH81uYF0,2215
|
|
27
|
+
universal_mcp/agents/codeact0/langgraph_agent.py,sha256=8nz2wq-LexImx-l1y9_f81fK72IQetnCeljwgnduNGY,420
|
|
28
|
+
universal_mcp/agents/codeact0/llm_tool.py,sha256=-pAz04OrbZ_dJ2ueysT1qZd02DrbLY4EbU0tiuF_UNU,798
|
|
29
|
+
universal_mcp/agents/codeact0/prompts.py,sha256=6yL-u8LG5Xf5HWgG6zXTK-tLbHczh06tBi8m61ZYOW8,17830
|
|
30
|
+
universal_mcp/agents/codeact0/sandbox.py,sha256=WgJ90FHOA4LPTdIcazMN4nMUTYum_nsxwmam8kAsNSM,4688
|
|
31
|
+
universal_mcp/agents/codeact0/state.py,sha256=cf-94hfVub-HSQJk6b7_SzqBS-oxMABjFa8jqyjdDK0,1925
|
|
32
|
+
universal_mcp/agents/codeact0/tools.py,sha256=0rCNzTwGz6blwbY7vgUkvEpTnN2cYYYESfNh960cPbM,23277
|
|
33
|
+
universal_mcp/agents/codeact0/utils.py,sha256=6tfMHa09Kjd2Qf98VpDN-rW-UXqmAry96Rg0SAu7PWM,21809
|
|
34
|
+
universal_mcp/agents/codeact00/__init__.py,sha256=8-fvUo1Sm6dURGI-lW-X3Kd78LqySYbb5NMkNJ4NDwg,76
|
|
35
|
+
universal_mcp/agents/codeact00/__main__.py,sha256=VJyUxL6Jn7DioBLb9KILybPwX4Mh-v0REQfQb84W-EY,767
|
|
36
|
+
universal_mcp/agents/codeact00/agent.py,sha256=v_3RjPSZMTjij2ZsMj92P-lM80nRQrMmNaSCEoBbtfo,28737
|
|
37
|
+
universal_mcp/agents/codeact00/config.py,sha256=H-1woj_nhSDwf15F63WYn723y4qlRefXzGxuH81uYF0,2215
|
|
38
|
+
universal_mcp/agents/codeact00/langgraph_agent.py,sha256=mGRNjFB4YoRoVZKkAlV8ot1MeUDlf2A43eB-Cjlgj3M,421
|
|
39
|
+
universal_mcp/agents/codeact00/llm_tool.py,sha256=ndqRZoXxX2rXS6vMK_xKnG3nxLWXs-Ee_Eh2TLHWjdw,799
|
|
40
|
+
universal_mcp/agents/codeact00/prompts.py,sha256=EJ_M3MUq9nymuT93YZEqj_QopFC3O1pHkDw3jXOFPl4,22892
|
|
41
|
+
universal_mcp/agents/codeact00/sandbox.py,sha256=klJmbQWejMYZYOdx5g2UoX8BCq7oIgwSHk1jVaPeLug,4689
|
|
42
|
+
universal_mcp/agents/codeact00/state.py,sha256=GF6QEWC-8useTI3JxYCfXS3h9HZBIWoUVp8qKDsmyYY,2125
|
|
43
|
+
universal_mcp/agents/codeact00/tools.py,sha256=7CAEcrIcqKQ2rDTDR619bLdOcjJGaTdXe5TrsHWMOUI,23615
|
|
44
|
+
universal_mcp/agents/codeact00/utils.py,sha256=PyG0qi5ViPE9yqEFLs_hAkgwqdO8jwYVD03on3SVCfM,26222
|
|
45
|
+
universal_mcp/agents/codeact01/__init__.py,sha256=8-fvUo1Sm6dURGI-lW-X3Kd78LqySYbb5NMkNJ4NDwg,76
|
|
46
|
+
universal_mcp/agents/codeact01/__main__.py,sha256=XBKqo1iU2LYMleXh2yoQFlZNJeLnML801vVzVyxcnMU,767
|
|
47
|
+
universal_mcp/agents/codeact01/agent.py,sha256=Nw3t51jU-pOnj9v1kBMpDgoBghOHaTdiMRmc2mrPXJs,19743
|
|
48
|
+
universal_mcp/agents/codeact01/config.py,sha256=H-1woj_nhSDwf15F63WYn723y4qlRefXzGxuH81uYF0,2215
|
|
49
|
+
universal_mcp/agents/codeact01/langgraph_agent.py,sha256=jevPeluYRm3mpfD9XcwS9Fnn7cMa6YILH6ITP18uUrw,421
|
|
50
|
+
universal_mcp/agents/codeact01/llm_tool.py,sha256=Yw65868xGsbOii8JGp2mePdNVFZta0ZmLC1Fjub3SGw,799
|
|
51
|
+
universal_mcp/agents/codeact01/prompts.py,sha256=C3MJ1GBAo1qT5_ByDlyscbcRm32Z3L1Px5lvmFBLtvM,13980
|
|
52
|
+
universal_mcp/agents/codeact01/sandbox.py,sha256=XgbClsuSauGNYvnETYXBtRlkUUFr8yEHoTOJYwKtBPI,6907
|
|
53
|
+
universal_mcp/agents/codeact01/state.py,sha256=cf-94hfVub-HSQJk6b7_SzqBS-oxMABjFa8jqyjdDK0,1925
|
|
54
|
+
universal_mcp/agents/codeact01/tools.py,sha256=Nh7YBrnk7jxel5mPga75Kwgt1a-v5NIvHz_yH3CaFJc,32538
|
|
55
|
+
universal_mcp/agents/codeact01/utils.py,sha256=SlyzpfXDoaWC3MjRZi7x1yxDkL63ZVzHk18XICtdM54,21810
|
|
56
|
+
universal_mcp/agents/shared/__main__.py,sha256=XxH5qGDpgFWfq7fwQfgKULXGiUgeTp_YKfcxftuVZq8,1452
|
|
57
|
+
universal_mcp/agents/shared/prompts.py,sha256=yjP3zbbuKi87qCj21qwTTicz8TqtkKgnyGSeEjMu3ho,3761
|
|
58
|
+
universal_mcp/agents/shared/tool_node.py,sha256=DC9F-Ri28Pam0u3sXWNODVgmj9PtAEUb5qP1qOoGgfs,9169
|
|
59
|
+
universal_mcp/applications/filesystem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
+
universal_mcp/applications/filesystem/app.py,sha256=0TRjjm8YnslVRSmfkXI7qQOAlqWlD1eEn8Jm0xBeigs,5561
|
|
61
|
+
universal_mcp/applications/llm/__init__.py,sha256=_XGRxN3O1--ZS5joAsPf8IlI9Qa6negsJrwJ5VJXno0,46
|
|
62
|
+
universal_mcp/applications/llm/app.py,sha256=eM1BUQefxl1qsNKWCnYQlMg-cTu8HpX49spLHUBpMTY,15283
|
|
63
|
+
universal_mcp/applications/ui/app.py,sha256=c7OkZsO2fRtndgAzAQbKu-1xXRuRp9Kjgml57YD2NR4,9459
|
|
64
|
+
universal_mcp_agents-0.1.24rc3.dist-info/METADATA,sha256=BAiJYrW-eiF3xlPpr-FiFjOxGN7k4mBTOMT-S9SJxDs,931
|
|
65
|
+
universal_mcp_agents-0.1.24rc3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
66
|
+
universal_mcp_agents-0.1.24rc3.dist-info/RECORD,,
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import ast
|
|
2
|
-
from collections.abc import Callable
|
|
3
|
-
|
|
4
|
-
from langchain_core.messages import AIMessage, HumanMessage
|
|
5
|
-
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
6
|
-
from langgraph.graph import END, START, StateGraph
|
|
7
|
-
from langgraph.types import Command
|
|
8
|
-
from pydantic import BaseModel, Field
|
|
9
|
-
from universal_mcp.logger import logger
|
|
10
|
-
from universal_mcp.tools.registry import ToolRegistry
|
|
11
|
-
from universal_mcp.types import ToolConfig, ToolFormat
|
|
12
|
-
|
|
13
|
-
from universal_mcp.agents.base import BaseAgent
|
|
14
|
-
from universal_mcp.agents.codeact.models import SandboxOutput
|
|
15
|
-
from universal_mcp.agents.codeact.prompts import (
|
|
16
|
-
create_default_prompt,
|
|
17
|
-
make_safe_function_name,
|
|
18
|
-
)
|
|
19
|
-
from universal_mcp.agents.codeact.sandbox import eval_unsafe
|
|
20
|
-
from universal_mcp.agents.codeact.state import CodeActState
|
|
21
|
-
from universal_mcp.agents.llm import load_chat_model
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class StructuredCodeResponse(BaseModel):
|
|
25
|
-
"""Structured response for the CodeAct agent."""
|
|
26
|
-
|
|
27
|
-
reasoning: str = Field(..., description="The reasoning behind the generated script.")
|
|
28
|
-
script: str | None = Field(default=None, description="The Python script to be executed.")
|
|
29
|
-
task_complete: bool = Field(..., description="Whether the task is complete.")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class CodeActAgent(BaseAgent):
|
|
33
|
-
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
name: str,
|
|
36
|
-
instructions: str,
|
|
37
|
-
model: str,
|
|
38
|
-
memory: BaseCheckpointSaver | None = None,
|
|
39
|
-
tools: ToolConfig | None = None,
|
|
40
|
-
registry: ToolRegistry | None = None,
|
|
41
|
-
sandbox_timeout: int = 20,
|
|
42
|
-
**kwargs,
|
|
43
|
-
):
|
|
44
|
-
super().__init__(
|
|
45
|
-
name=name,
|
|
46
|
-
instructions=instructions,
|
|
47
|
-
model=model,
|
|
48
|
-
memory=memory,
|
|
49
|
-
**kwargs,
|
|
50
|
-
)
|
|
51
|
-
self.model_instance = load_chat_model(model)
|
|
52
|
-
self.tools_config = tools or {}
|
|
53
|
-
self.registry = registry
|
|
54
|
-
self.eval_fn = eval_unsafe
|
|
55
|
-
self.sandbox_timeout = sandbox_timeout
|
|
56
|
-
self.processed_tools: dict[str, Callable] = {}
|
|
57
|
-
|
|
58
|
-
async def _build_graph(self):
|
|
59
|
-
if self.tools_config:
|
|
60
|
-
if not self.registry:
|
|
61
|
-
raise ValueError("Tools are configured but no registry is provided")
|
|
62
|
-
# Load native tools, these are python functions
|
|
63
|
-
exported_tools = await self.registry.export_tools(self.tools_config, ToolFormat.NATIVE)
|
|
64
|
-
for tool in exported_tools:
|
|
65
|
-
name = tool.__name__
|
|
66
|
-
safe_name = make_safe_function_name(name)
|
|
67
|
-
if name != safe_name:
|
|
68
|
-
logger.warning(f"Tool name {name} is not safe, using {safe_name} instead")
|
|
69
|
-
raise ValueError(f"Tool name {name} is not safe, using {safe_name} instead")
|
|
70
|
-
self.processed_tools[safe_name] = tool
|
|
71
|
-
|
|
72
|
-
self.instructions = create_default_prompt(self.processed_tools, self.instructions)
|
|
73
|
-
|
|
74
|
-
agent = StateGraph(CodeActState)
|
|
75
|
-
agent.add_node("call_model", self.call_model)
|
|
76
|
-
agent.add_node("validate_code", self.validate_code)
|
|
77
|
-
agent.add_node("sandbox", self.sandbox)
|
|
78
|
-
agent.add_node("final_answer", self.final_answer)
|
|
79
|
-
|
|
80
|
-
agent.add_edge(START, "call_model")
|
|
81
|
-
|
|
82
|
-
return agent.compile(checkpointer=self.memory)
|
|
83
|
-
|
|
84
|
-
async def call_model(self, state: CodeActState) -> Command:
|
|
85
|
-
logger.debug(f"Calling model with state: {state}")
|
|
86
|
-
model = self.model_instance.with_structured_output(StructuredCodeResponse)
|
|
87
|
-
|
|
88
|
-
# Find the last script and its output in the message history
|
|
89
|
-
previous_script = state.get("script", "")
|
|
90
|
-
sandbox_output = state.get("sandbox_output", "")
|
|
91
|
-
syntax_error = state.get("syntax_error", "")
|
|
92
|
-
|
|
93
|
-
logger.debug(f"Previous script:\n {previous_script}")
|
|
94
|
-
logger.debug(f"Sandbox output:\n {sandbox_output}")
|
|
95
|
-
logger.debug(f"Syntax error:\n {syntax_error}")
|
|
96
|
-
|
|
97
|
-
prompt_messages = [
|
|
98
|
-
{"role": "system", "content": self.instructions},
|
|
99
|
-
*state["messages"],
|
|
100
|
-
]
|
|
101
|
-
if previous_script:
|
|
102
|
-
feedback_message = (
|
|
103
|
-
f"Here is the script you generated in the last turn:\n\n```python\n{previous_script}\n```\n\n"
|
|
104
|
-
)
|
|
105
|
-
if syntax_error:
|
|
106
|
-
feedback_message += (
|
|
107
|
-
f"When parsing the script, it produced the following syntax error:\n\n```\n{syntax_error}\n```\n\n"
|
|
108
|
-
"Please fix the syntax and generate a new, correct script."
|
|
109
|
-
)
|
|
110
|
-
elif sandbox_output:
|
|
111
|
-
feedback_message += (
|
|
112
|
-
f"When executed, it produced the following output:\n\n```\n{sandbox_output}\n```\n\n"
|
|
113
|
-
)
|
|
114
|
-
feedback_message += "Based on this output, decide if the task is complete. If it is, respond the final answer to the user in clean and readable Markdown format. Important: set `task_complete` to `True` and no need to provide script. If the task is not complete, generate a new script to get closer to the solution."
|
|
115
|
-
|
|
116
|
-
prompt_messages.append({"role": "user", "content": feedback_message})
|
|
117
|
-
|
|
118
|
-
response: StructuredCodeResponse = await model.ainvoke(prompt_messages)
|
|
119
|
-
|
|
120
|
-
# We add the reasoning as the AI message content
|
|
121
|
-
ai_message = AIMessage(content=response.reasoning)
|
|
122
|
-
|
|
123
|
-
if response.task_complete:
|
|
124
|
-
return Command(
|
|
125
|
-
goto="final_answer",
|
|
126
|
-
update={
|
|
127
|
-
"messages": [ai_message],
|
|
128
|
-
"script": response.script,
|
|
129
|
-
"task_complete": response.task_complete,
|
|
130
|
-
"sandbox_output": sandbox_output,
|
|
131
|
-
"syntax_error": None,
|
|
132
|
-
},
|
|
133
|
-
)
|
|
134
|
-
else:
|
|
135
|
-
return Command(
|
|
136
|
-
goto="validate_code",
|
|
137
|
-
update={
|
|
138
|
-
"messages": [ai_message],
|
|
139
|
-
"script": response.script,
|
|
140
|
-
"task_complete": response.task_complete,
|
|
141
|
-
"sandbox_output": None,
|
|
142
|
-
"syntax_error": None,
|
|
143
|
-
},
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
async def validate_code(self, state: CodeActState) -> Command:
|
|
147
|
-
logger.debug(f"Validating code with script:\n {state['script']}")
|
|
148
|
-
script = state.get("script")
|
|
149
|
-
|
|
150
|
-
if not script:
|
|
151
|
-
return Command(
|
|
152
|
-
goto="call_model",
|
|
153
|
-
update={
|
|
154
|
-
"syntax_error": "Model did not provide a script but task is not complete. Please provide a script or set task_complete to True."
|
|
155
|
-
},
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
try:
|
|
159
|
-
ast.parse(script)
|
|
160
|
-
logger.debug("AST parsing successful.")
|
|
161
|
-
return Command(
|
|
162
|
-
goto="sandbox",
|
|
163
|
-
update={
|
|
164
|
-
"syntax_error": None,
|
|
165
|
-
},
|
|
166
|
-
)
|
|
167
|
-
except SyntaxError as e:
|
|
168
|
-
logger.warning(f"AST parsing failed: {e}")
|
|
169
|
-
return Command(
|
|
170
|
-
goto="call_model",
|
|
171
|
-
update={
|
|
172
|
-
"syntax_error": f"Syntax Error: {e}",
|
|
173
|
-
},
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
async def sandbox(self, state: CodeActState) -> Command:
|
|
177
|
-
logger.debug(f"Running sandbox with script:\n {state['script']}")
|
|
178
|
-
tools_context = {}
|
|
179
|
-
for tool_name, tool_callable in self.processed_tools.items():
|
|
180
|
-
tools_context[tool_name] = tool_callable
|
|
181
|
-
|
|
182
|
-
output: SandboxOutput
|
|
183
|
-
output, _ = await self.eval_fn(state["script"], tools_context, self.sandbox_timeout)
|
|
184
|
-
|
|
185
|
-
# Format the output for the agent
|
|
186
|
-
formatted_output = "Code executed.\n\n"
|
|
187
|
-
MAX_OUTPUT_LEN = 20000 # Maximum number of characters to show for stdout/stderr
|
|
188
|
-
|
|
189
|
-
def truncate_output(text, max_len=MAX_OUTPUT_LEN):
|
|
190
|
-
if text is None:
|
|
191
|
-
return ""
|
|
192
|
-
text = text.strip()
|
|
193
|
-
if len(text) > max_len:
|
|
194
|
-
return text[:max_len] + "\n... (more output hidden)"
|
|
195
|
-
return text
|
|
196
|
-
|
|
197
|
-
if output.stdout:
|
|
198
|
-
truncated_stdout = truncate_output(output.stdout)
|
|
199
|
-
formatted_output += f"STDOUT:\n```\n{truncated_stdout}\n```\n\n"
|
|
200
|
-
if output.error:
|
|
201
|
-
truncated_stderr = truncate_output(output.error)
|
|
202
|
-
formatted_output += f"STDERR / ERROR:\n```\n{truncated_stderr}\n```\n"
|
|
203
|
-
if output.return_value is not None:
|
|
204
|
-
formatted_output += f"RETURN VALUE:\n```\n{repr(output.return_value)}\n```\n"
|
|
205
|
-
|
|
206
|
-
logger.debug(f"Sandbox output: {formatted_output}")
|
|
207
|
-
return Command(
|
|
208
|
-
goto="call_model",
|
|
209
|
-
update={"sandbox_output": formatted_output.strip()},
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
async def final_answer(self, state: CodeActState) -> Command:
|
|
213
|
-
logger.debug("Formatting final answer using LLM for markdown formatting.")
|
|
214
|
-
|
|
215
|
-
# Extract the original user prompt
|
|
216
|
-
user_prompt = ""
|
|
217
|
-
for msg in state["messages"]:
|
|
218
|
-
if isinstance(msg, HumanMessage):
|
|
219
|
-
user_prompt = msg.content
|
|
220
|
-
break
|
|
221
|
-
|
|
222
|
-
# Compose a prompt for the LLM to generate a concise, markdown-formatted answer
|
|
223
|
-
llm_prompt = (
|
|
224
|
-
"Given the following task and answer, write a concise, well-formatted markdown response suitable for a user.\n\n"
|
|
225
|
-
f"Task:\n{user_prompt}\n\n"
|
|
226
|
-
f"Answer:\n{state['sandbox_output']}\n\n"
|
|
227
|
-
"Respond only with the markdown-formatted answer."
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
# Use the model to generate the final formatted answer
|
|
231
|
-
response = await self.model_instance.ainvoke([{"role": "user", "content": llm_prompt}])
|
|
232
|
-
markdown_answer = response.content if hasattr(response, "content") else str(response)
|
|
233
|
-
logger.debug(f"Final answer:\n {markdown_answer}")
|
|
234
|
-
|
|
235
|
-
return Command(
|
|
236
|
-
goto=END,
|
|
237
|
-
update={
|
|
238
|
-
"messages": [AIMessage(content=markdown_answer)],
|
|
239
|
-
},
|
|
240
|
-
)
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
import re
|
|
3
|
-
from collections.abc import Callable
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def make_safe_function_name(name: str) -> str:
|
|
7
|
-
"""Convert a tool name to a valid Python function name."""
|
|
8
|
-
# Replace non-alphanumeric characters with underscores
|
|
9
|
-
safe_name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
|
|
10
|
-
# Ensure the name doesn't start with a digit
|
|
11
|
-
if safe_name and safe_name[0].isdigit():
|
|
12
|
-
safe_name = f"tool_{safe_name}"
|
|
13
|
-
# Handle empty name edge case
|
|
14
|
-
if not safe_name:
|
|
15
|
-
safe_name = "unnamed_tool"
|
|
16
|
-
return safe_name
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def create_default_prompt(
|
|
20
|
-
tools: dict[str, Callable],
|
|
21
|
-
base_prompt: str | None = None,
|
|
22
|
-
):
|
|
23
|
-
"""Create default prompt for the CodeAct agent."""
|
|
24
|
-
prompt = f"{base_prompt}\n\n" if base_prompt else ""
|
|
25
|
-
prompt += """You are a Python programmer. You will be given a task to perform.
|
|
26
|
-
Your goal is to write a self-contained Python script to accomplish the task.
|
|
27
|
-
|
|
28
|
-
In each turn, you will generate a complete Python script. The script will be executed in a fresh, stateless environment.
|
|
29
|
-
You will be given the previous script you generated and the output it produced.
|
|
30
|
-
Your task is to analyze the output to find errors or opportunities for improvement, and then generate a new, improved script.
|
|
31
|
-
You must take the previous script as a starting point and replace it with a new one that moves closer to the final solution.
|
|
32
|
-
Your final script must be a single, complete piece of code that can be executed independently.
|
|
33
|
-
|
|
34
|
-
The script must follow this structure:
|
|
35
|
-
1. All necessary imports at the top.
|
|
36
|
-
2. An `async def main():` function containing the core logic.
|
|
37
|
-
3. Do NOT include any code outside of the `async def main()` function, and do NOT call it. The execution environment handles this.
|
|
38
|
-
|
|
39
|
-
Any output you want to see from the code should be printed to the console from within the `main` function.
|
|
40
|
-
Code should be output in a fenced code block (e.g. ```python ... ```).
|
|
41
|
-
|
|
42
|
-
If you need to ask for more information or provide the final answer, you can output text to be shown directly to the user.
|
|
43
|
-
|
|
44
|
-
In addition to the Python Standard Library, you can use the following functions:"""
|
|
45
|
-
|
|
46
|
-
for tool_name, tool_callable in tools.items():
|
|
47
|
-
# Determine if it's an async function
|
|
48
|
-
is_async = inspect.iscoroutinefunction(tool_callable)
|
|
49
|
-
# Add appropriate function definition
|
|
50
|
-
prompt += f'''\n{"async " if is_async else ""}def {tool_name}{str(inspect.signature(tool_callable))}:
|
|
51
|
-
"""{tool_callable.__doc__}"""
|
|
52
|
-
...
|
|
53
|
-
'''
|
|
54
|
-
|
|
55
|
-
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'.
|
|
56
|
-
|
|
57
|
-
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.
|
|
58
|
-
|
|
59
|
-
IMPORTANT CODING STRATEGY:
|
|
60
|
-
1. All your code must be inside an `async def main()` function.
|
|
61
|
-
2. Do NOT import `asyncio` or call `main()`. The execution environment handles this.
|
|
62
|
-
3. Since many of the provided tools are async, you must use `await` to call them from within `main()`.
|
|
63
|
-
4. Write code up to the point where you make an API call/tool usage with an output.
|
|
64
|
-
5. Print the type/shape and a sample entry of this output, and using that knowledge proceed to write the further code.
|
|
65
|
-
6. The maximum number of characters that can be printed is 5000. Remove any unnecessary print statements.
|
|
66
|
-
|
|
67
|
-
This means:
|
|
68
|
-
- Write code that makes the API call or tool usage
|
|
69
|
-
- Print the result with type information: print(f"Type: {type(result)}")
|
|
70
|
-
- 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'}")
|
|
71
|
-
- Print a sample entry: print(f"Sample: {result[0] if isinstance(result, (list, tuple)) and len(result) > 0 else result}")
|
|
72
|
-
- Then, based on this knowledge, write the code to process/use this data
|
|
73
|
-
|
|
74
|
-
Reminder: use Python code snippets to call tools
|
|
75
|
-
|
|
76
|
-
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.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
Important:
|
|
80
|
-
After you have provided the final output, you MUST set `task_complete` to `True` in your response.
|
|
81
|
-
"""
|
|
82
|
-
return prompt
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import contextlib
|
|
3
|
-
import io
|
|
4
|
-
from collections.abc import Callable
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from loguru import logger
|
|
8
|
-
|
|
9
|
-
from .models import SandboxOutput
|
|
10
|
-
|
|
11
|
-
# Define a whitelist of safe built-in functions
|
|
12
|
-
SAFE_BUILTINS = {
|
|
13
|
-
"abs": abs,
|
|
14
|
-
"all": all,
|
|
15
|
-
"any": any,
|
|
16
|
-
"bool": bool,
|
|
17
|
-
"callable": callable,
|
|
18
|
-
"chr": chr,
|
|
19
|
-
"dict": dict,
|
|
20
|
-
"divmod": divmod,
|
|
21
|
-
"enumerate": enumerate,
|
|
22
|
-
"filter": filter,
|
|
23
|
-
"float": float,
|
|
24
|
-
"getattr": getattr,
|
|
25
|
-
"hasattr": hasattr,
|
|
26
|
-
"hash": hash,
|
|
27
|
-
"id": id,
|
|
28
|
-
"int": int,
|
|
29
|
-
"isinstance": isinstance,
|
|
30
|
-
"iter": iter,
|
|
31
|
-
"len": len,
|
|
32
|
-
"list": list,
|
|
33
|
-
"max": max,
|
|
34
|
-
"min": min,
|
|
35
|
-
"next": next,
|
|
36
|
-
"ord": ord,
|
|
37
|
-
"pow": pow,
|
|
38
|
-
"print": print,
|
|
39
|
-
"range": range,
|
|
40
|
-
"repr": repr,
|
|
41
|
-
"reversed": reversed,
|
|
42
|
-
"round": round,
|
|
43
|
-
"set": set,
|
|
44
|
-
"slice": slice,
|
|
45
|
-
"sorted": sorted,
|
|
46
|
-
"str": str,
|
|
47
|
-
"sum": sum,
|
|
48
|
-
"tuple": tuple,
|
|
49
|
-
"type": type,
|
|
50
|
-
"zip": zip,
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
async def eval_unsafe(
|
|
55
|
-
code: str, _locals: dict[str, Callable], timeout: int = 10
|
|
56
|
-
) -> tuple[SandboxOutput, dict[str, Any]]:
|
|
57
|
-
"""Executes a string of Python code in a sandboxed environment."""
|
|
58
|
-
original_keys = set(_locals.keys())
|
|
59
|
-
execution_context = _locals.copy()
|
|
60
|
-
execution_context["__builtins__"] = __builtins__ # TODO: Use SAFE_BUILTINS instead of __builtins__
|
|
61
|
-
|
|
62
|
-
stdout_capture = io.StringIO()
|
|
63
|
-
output = SandboxOutput(stdout="")
|
|
64
|
-
|
|
65
|
-
try:
|
|
66
|
-
logger.debug(f"Executing code with timeout {timeout}")
|
|
67
|
-
with contextlib.redirect_stdout(stdout_capture):
|
|
68
|
-
exec(code, execution_context)
|
|
69
|
-
|
|
70
|
-
if "main" in execution_context and asyncio.iscoroutinefunction(execution_context["main"]):
|
|
71
|
-
return_val = await asyncio.wait_for(execution_context["main"](), timeout=timeout)
|
|
72
|
-
output.return_value = return_val
|
|
73
|
-
else:
|
|
74
|
-
output.error = "No `async def main()` function found in the script."
|
|
75
|
-
|
|
76
|
-
output.stdout = stdout_capture.getvalue()
|
|
77
|
-
|
|
78
|
-
except Exception as e:
|
|
79
|
-
output.error = f"{type(e).__name__}: {e}"
|
|
80
|
-
output.stdout = stdout_capture.getvalue()
|
|
81
|
-
|
|
82
|
-
new_keys = set(execution_context.keys()) - original_keys - {"__builtins__"}
|
|
83
|
-
new_vars = {key: execution_context[key] for key in new_keys}
|
|
84
|
-
|
|
85
|
-
return output, new_vars
|
|
@@ -1,11 +0,0 @@
|
|
|
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.")
|
|
10
|
-
syntax_error: str | None = Field(default=None, description="The syntax error from the last script validation.")
|
|
11
|
-
task_complete: bool = Field(default=False, description="Whether the task is complete.")
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
from universal_mcp.logger import logger
|
|
4
|
-
|
|
5
|
-
BACKTICK_PATTERN = r"(?:^|\n)```(.*?)(?:```(?:\n|$))"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def extract_and_combine_codeblocks(text: str) -> str:
|
|
9
|
-
"""
|
|
10
|
-
Extracts all codeblocks from a text string and combines them into a single code string.
|
|
11
|
-
|
|
12
|
-
Args:
|
|
13
|
-
text: A string containing zero or more codeblocks, where each codeblock is
|
|
14
|
-
surrounded by triple backticks (```).
|
|
15
|
-
|
|
16
|
-
Returns:
|
|
17
|
-
A string containing the combined code from all codeblocks, with each codeblock
|
|
18
|
-
separated by a newline.
|
|
19
|
-
|
|
20
|
-
Example:
|
|
21
|
-
text = '''Here's some code:
|
|
22
|
-
|
|
23
|
-
```python
|
|
24
|
-
print('hello')
|
|
25
|
-
```
|
|
26
|
-
And more:
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
print('world')
|
|
30
|
-
```'''
|
|
31
|
-
|
|
32
|
-
result = extract_and_combine_codeblocks(text)
|
|
33
|
-
|
|
34
|
-
Result:
|
|
35
|
-
|
|
36
|
-
print('hello')
|
|
37
|
-
|
|
38
|
-
print('world')
|
|
39
|
-
"""
|
|
40
|
-
# Find all code blocks in the text using regex
|
|
41
|
-
# Pattern matches anything between triple backticks, with or without a language identifier
|
|
42
|
-
try:
|
|
43
|
-
code_blocks = re.findall(BACKTICK_PATTERN, text, re.DOTALL)
|
|
44
|
-
except Exception as e:
|
|
45
|
-
logger.error(f"Error extracting code blocks: {e}")
|
|
46
|
-
logger.error(f"Text: {text}")
|
|
47
|
-
return ""
|
|
48
|
-
|
|
49
|
-
if not code_blocks:
|
|
50
|
-
return ""
|
|
51
|
-
|
|
52
|
-
# Process each codeblock
|
|
53
|
-
processed_blocks = []
|
|
54
|
-
for block in code_blocks:
|
|
55
|
-
# Strip leading and trailing whitespace
|
|
56
|
-
cleaned_block = block.strip()
|
|
57
|
-
|
|
58
|
-
# If the first line looks like a language identifier, remove it
|
|
59
|
-
lines = cleaned_block.split("\n")
|
|
60
|
-
if lines and (not lines[0].strip() or " " not in lines[0].strip()):
|
|
61
|
-
# First line is empty or likely a language identifier (no spaces)
|
|
62
|
-
cleaned_block = "\n".join(lines[1:])
|
|
63
|
-
|
|
64
|
-
processed_blocks.append(cleaned_block)
|
|
65
|
-
|
|
66
|
-
# Combine all codeblocks with newlines between them
|
|
67
|
-
combined_code = "\n\n".join(processed_blocks)
|
|
68
|
-
return combined_code
|