universal-mcp-agents 0.1.19rc1__tar.gz → 0.1.20rc1__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.
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/PKG-INFO +1 -1
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/pyproject.toml +1 -1
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/__init__.py +5 -9
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/base.py +4 -1
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/cli.py +0 -3
- universal_mcp_agents-0.1.20rc1/src/universal_mcp/agents/codeact0/__init__.py +3 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/__main__.py +2 -2
- {universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/unified → universal_mcp_agents-0.1.20rc1/src/universal_mcp/agents/codeact0}/agent.py +79 -76
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/langgraph_agent.py +1 -1
- {universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/unified → universal_mcp_agents-0.1.20rc1/src/universal_mcp/agents/codeact0}/prompts.py +58 -40
- {universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/unified → universal_mcp_agents-0.1.20rc1/src/universal_mcp/agents/codeact0}/sandbox.py +31 -1
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/state.py +3 -1
- universal_mcp_agents-0.1.20rc1/src/universal_mcp/agents/codeact0/tools.py +303 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/uv.lock +1 -1
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact/__init__.py +0 -3
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact/__main__.py +0 -33
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact/agent.py +0 -240
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact/models.py +0 -11
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact/prompts.py +0 -82
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact/sandbox.py +0 -85
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact/state.py +0 -11
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact/utils.py +0 -68
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact0/__init__.py +0 -4
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact0/agent.py +0 -144
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact0/playbook_agent.py +0 -355
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact0/prompts.py +0 -177
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact0/sandbox.py +0 -101
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/codeact0/tools.py +0 -188
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/unified/README.md +0 -45
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/unified/__init__.py +0 -3
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/unified/__main__.py +0 -28
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/unified/langgraph_agent.py +0 -14
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/unified/llm_tool.py +0 -25
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/unified/state.py +0 -42
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/unified/tools.py +0 -188
- universal_mcp_agents-0.1.19rc1/src/universal_mcp/agents/unified/utils.py +0 -388
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/.github/workflows/evals.yml +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/.github/workflows/lint.yml +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/.github/workflows/release-please.yml +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/.github/workflows/tests.yml +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/.gitignore +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/.pre-commit-config.yaml +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/GEMINI.md +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/PROMPTS.md +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/README.md +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/bump_and_release.sh +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/evals/__init__.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/evals/dataset.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/evals/datasets/codeact.jsonl +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/evals/datasets/exact.jsonl +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/evals/datasets/tasks.jsonl +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/evals/evaluators.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/evals/prompts.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/evals/run.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/evals/utils.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/tests/test_agents.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/__init__.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/__main__.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/agent.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/context.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/graph.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/prompts.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/state.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/tools.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/builder/__main__.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/builder/builder.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/builder/helper.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/builder/prompts.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/builder/state.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/config.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/llm_tool.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/utils.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/hil.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/llm.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/react.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/sandbox.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/shared/__main__.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/shared/prompts.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/shared/tool_node.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/simple.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/utils.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/applications/filesystem/__init__.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/applications/filesystem/app.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/applications/llm/__init__.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/applications/llm/app.py +0 -0
- {universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/applications/ui/app.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: universal-mcp-agents
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.20rc1
|
|
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
|
|
@@ -3,14 +3,14 @@ from typing import Literal
|
|
|
3
3
|
from universal_mcp.agents.base import BaseAgent
|
|
4
4
|
from universal_mcp.agents.bigtool import BigToolAgent
|
|
5
5
|
from universal_mcp.agents.builder.builder import BuilderAgent
|
|
6
|
-
from universal_mcp.agents.
|
|
7
|
-
from universal_mcp.agents.codeact0 import CodeActPlaybookAgent as CodeActRepl
|
|
6
|
+
from universal_mcp.agents.codeact0 import CodeActPlaybookAgent
|
|
8
7
|
from universal_mcp.agents.react import ReactAgent
|
|
9
8
|
from universal_mcp.agents.simple import SimpleAgent
|
|
10
|
-
from universal_mcp.agents.unified import UnifiedAgent
|
|
11
9
|
|
|
12
10
|
|
|
13
|
-
def get_agent(
|
|
11
|
+
def get_agent(
|
|
12
|
+
agent_name: Literal["react", "simple", "builder", "bigtool", "codeact-script", "codeact-repl"],
|
|
13
|
+
):
|
|
14
14
|
if agent_name == "react":
|
|
15
15
|
return ReactAgent
|
|
16
16
|
elif agent_name == "simple":
|
|
@@ -19,12 +19,8 @@ def get_agent(agent_name: Literal["react", "simple", "builder", "bigtool", "code
|
|
|
19
19
|
return BuilderAgent
|
|
20
20
|
elif agent_name == "bigtool":
|
|
21
21
|
return BigToolAgent
|
|
22
|
-
elif agent_name == "codeact-script":
|
|
23
|
-
return CodeActScript
|
|
24
22
|
elif agent_name == "codeact-repl":
|
|
25
|
-
return
|
|
26
|
-
elif agent_name == "unified":
|
|
27
|
-
return UnifiedAgent
|
|
23
|
+
return CodeActPlaybookAgent
|
|
28
24
|
else:
|
|
29
25
|
raise ValueError(
|
|
30
26
|
f"Unknown agent: {agent_name}. Possible values: react, simple, builder, bigtool, codeact-script, codeact-repl"
|
{universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/base.py
RENAMED
|
@@ -49,8 +49,11 @@ class BaseAgent:
|
|
|
49
49
|
run_metadata.update(metadata)
|
|
50
50
|
|
|
51
51
|
run_config = {
|
|
52
|
+
"recursion_limit": 50,
|
|
52
53
|
"configurable": {"thread_id": thread_id},
|
|
53
54
|
"metadata": run_metadata,
|
|
55
|
+
"run_id": thread_id,
|
|
56
|
+
"run_name": self.name,
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
async for event, meta in self._graph.astream(
|
|
@@ -113,7 +116,7 @@ class BaseAgent:
|
|
|
113
116
|
run_metadata.update(metadata)
|
|
114
117
|
|
|
115
118
|
run_config = {
|
|
116
|
-
"recursion_limit":
|
|
119
|
+
"recursion_limit": 50,
|
|
117
120
|
"configurable": {"thread_id": thread_id},
|
|
118
121
|
"metadata": run_metadata,
|
|
119
122
|
"run_id": thread_id,
|
{universal_mcp_agents-0.1.19rc1 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/cli.py
RENAMED
|
@@ -28,9 +28,6 @@ def run(name: str = "react"):
|
|
|
28
28
|
"model": "anthropic/claude-sonnet-4-20250514",
|
|
29
29
|
"registry": AgentrRegistry(client=client),
|
|
30
30
|
"memory": MemorySaver(),
|
|
31
|
-
"tools": {
|
|
32
|
-
"google_mail": ["send_email"],
|
|
33
|
-
},
|
|
34
31
|
}
|
|
35
32
|
agent_cls = get_agent(name)
|
|
36
33
|
agent = agent_cls(name=name, **params)
|
|
@@ -4,13 +4,13 @@ from langgraph.checkpoint.memory import MemorySaver
|
|
|
4
4
|
from rich import print
|
|
5
5
|
from universal_mcp.agentr.registry import AgentrRegistry
|
|
6
6
|
|
|
7
|
-
from universal_mcp.agents.codeact0.agent import
|
|
7
|
+
from universal_mcp.agents.codeact0.agent import CodeActPlaybookAgent
|
|
8
8
|
from universal_mcp.agents.utils import messages_to_list
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
async def main():
|
|
12
12
|
memory = MemorySaver()
|
|
13
|
-
agent =
|
|
13
|
+
agent = CodeActPlaybookAgent(
|
|
14
14
|
name="CodeAct Agent",
|
|
15
15
|
instructions="Be very concise in your answers.",
|
|
16
16
|
model="anthropic:claude-4-sonnet-20250514",
|
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import re
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from typing import Literal, cast
|
|
4
5
|
|
|
5
6
|
from langchain_core.messages import AIMessage, ToolMessage
|
|
6
7
|
from langchain_core.tools import StructuredTool
|
|
7
|
-
from langchain_core.tools import tool as create_tool
|
|
8
8
|
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
9
9
|
from langgraph.graph import START, StateGraph
|
|
10
10
|
from langgraph.types import Command, RetryPolicy
|
|
11
|
-
from loguru import logger
|
|
12
11
|
from universal_mcp.tools.registry import ToolRegistry
|
|
13
12
|
from universal_mcp.types import ToolConfig, ToolFormat
|
|
14
13
|
|
|
15
14
|
from universal_mcp.agents.base import BaseAgent
|
|
16
|
-
from universal_mcp.agents.
|
|
17
|
-
from universal_mcp.agents.
|
|
18
|
-
|
|
19
|
-
from .llm_tool import smart_print
|
|
20
|
-
from .prompts import (
|
|
15
|
+
from universal_mcp.agents.codeact0.llm_tool import ai_classify, call_llm, data_extractor, smart_print
|
|
16
|
+
from universal_mcp.agents.codeact0.prompts import (
|
|
21
17
|
PLAYBOOK_CONFIRMING_PROMPT,
|
|
22
18
|
PLAYBOOK_GENERATING_PROMPT,
|
|
23
19
|
PLAYBOOK_PLANNING_PROMPT,
|
|
24
20
|
create_default_prompt,
|
|
25
21
|
)
|
|
26
|
-
from .sandbox import eval_unsafe
|
|
27
|
-
from .state import CodeActState
|
|
28
|
-
from .tools import
|
|
29
|
-
|
|
22
|
+
from universal_mcp.agents.codeact0.sandbox import eval_unsafe, execute_ipython_cell, handle_execute_ipython_cell
|
|
23
|
+
from universal_mcp.agents.codeact0.state import CodeActState
|
|
24
|
+
from universal_mcp.agents.codeact0.tools import (
|
|
25
|
+
create_meta_tools,
|
|
26
|
+
enter_playbook_mode,
|
|
27
|
+
get_valid_tools,
|
|
28
|
+
)
|
|
29
|
+
from universal_mcp.agents.llm import load_chat_model
|
|
30
|
+
from universal_mcp.agents.utils import convert_tool_ids_to_dict, filter_retry_on, get_message_text
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
class
|
|
33
|
+
class CodeActPlaybookAgent(BaseAgent):
|
|
33
34
|
def __init__(
|
|
34
35
|
self,
|
|
35
36
|
name: str,
|
|
@@ -50,40 +51,35 @@ class UnifiedAgent(BaseAgent):
|
|
|
50
51
|
**kwargs,
|
|
51
52
|
)
|
|
52
53
|
self.model_instance = load_chat_model(model)
|
|
53
|
-
self.tools_config = tools or
|
|
54
|
+
self.tools_config = tools or []
|
|
54
55
|
self.registry = registry
|
|
55
56
|
self.playbook_registry = playbook_registry
|
|
56
|
-
self.sandbox_timeout = sandbox_timeout
|
|
57
57
|
self.eval_fn = eval_unsafe
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
self.sandbox_timeout = sandbox_timeout
|
|
59
|
+
self.processed_tools: list[StructuredTool | Callable] = []
|
|
60
60
|
|
|
61
|
-
async def _build_graph(self):
|
|
61
|
+
async def _build_graph(self):
|
|
62
62
|
meta_tools = create_meta_tools(self.registry)
|
|
63
|
-
additional_tools = [smart_print, meta_tools["web_search"]]
|
|
64
|
-
self.additional_tools = [
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
"role": "system",
|
|
80
|
-
"content": f"The last code execution resulted in this output:\n{state['output']}",
|
|
81
|
-
}
|
|
82
|
-
)
|
|
63
|
+
additional_tools = [smart_print, data_extractor, ai_classify, call_llm, meta_tools["web_search"]]
|
|
64
|
+
self.additional_tools = [
|
|
65
|
+
t if isinstance(t, StructuredTool) else StructuredTool.from_function(t) for t in additional_tools
|
|
66
|
+
]
|
|
67
|
+
if self.tools_config:
|
|
68
|
+
# Convert dict format to list format if needed
|
|
69
|
+
if isinstance(self.tools_config, dict):
|
|
70
|
+
self.tools_config = [
|
|
71
|
+
f"{provider}__{tool}" for provider, tools in self.tools_config.items() for tool in tools
|
|
72
|
+
]
|
|
73
|
+
if not self.registry:
|
|
74
|
+
raise ValueError("Tools are configured but no registry is provided")
|
|
75
|
+
|
|
76
|
+
async def call_model(state: CodeActState) -> Command[Literal["execute_tools"]]:
|
|
77
|
+
messages = [{"role": "system", "content": self.final_instructions}] + state["messages"]
|
|
83
78
|
|
|
84
79
|
# Run the model and potentially loop for reflection
|
|
85
80
|
model_with_tools = self.model_instance.bind_tools(
|
|
86
81
|
tools=[
|
|
82
|
+
execute_ipython_cell,
|
|
87
83
|
enter_playbook_mode,
|
|
88
84
|
meta_tools["search_functions"],
|
|
89
85
|
meta_tools["load_functions"],
|
|
@@ -91,18 +87,12 @@ class UnifiedAgent(BaseAgent):
|
|
|
91
87
|
tool_choice="auto",
|
|
92
88
|
)
|
|
93
89
|
response = cast(AIMessage, model_with_tools.invoke(messages))
|
|
94
|
-
|
|
95
|
-
code_match = re.search(r"```python\n(.*?)\n```", response_text, re.DOTALL)
|
|
96
|
-
|
|
97
|
-
if code_match:
|
|
98
|
-
code = code_match.group(1).strip()
|
|
99
|
-
return Command(goto="sandbox", update={"messages": [response], "code": code, "output": ""})
|
|
100
|
-
elif response.tool_calls:
|
|
90
|
+
if response.tool_calls:
|
|
101
91
|
return Command(goto="execute_tools", update={"messages": [response]})
|
|
102
92
|
else:
|
|
103
|
-
return Command(update={"messages": [response]})
|
|
93
|
+
return Command(update={"messages": [response], "model_with_tools": model_with_tools})
|
|
104
94
|
|
|
105
|
-
async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "playbook"
|
|
95
|
+
async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "playbook"]]:
|
|
106
96
|
"""Execute tool calls"""
|
|
107
97
|
last_message = state["messages"][-1]
|
|
108
98
|
tool_calls = last_message.tool_calls if isinstance(last_message, AIMessage) else []
|
|
@@ -112,6 +102,8 @@ class UnifiedAgent(BaseAgent):
|
|
|
112
102
|
ask_user = False
|
|
113
103
|
ai_msg = ""
|
|
114
104
|
tool_result = ""
|
|
105
|
+
effective_previous_add_context = state.get("add_context", {})
|
|
106
|
+
effective_existing_context = state.get("context", {})
|
|
115
107
|
|
|
116
108
|
for tool_call in tool_calls:
|
|
117
109
|
try:
|
|
@@ -125,6 +117,18 @@ class UnifiedAgent(BaseAgent):
|
|
|
125
117
|
goto="playbook",
|
|
126
118
|
update={"playbook_mode": "planning", "messages": [tool_message]}, # Entered Playbook mode
|
|
127
119
|
)
|
|
120
|
+
elif tool_call["name"] == "execute_ipython_cell":
|
|
121
|
+
code = tool_call["args"]["snippet"]
|
|
122
|
+
output, new_context, new_add_context = await handle_execute_ipython_cell(
|
|
123
|
+
code,
|
|
124
|
+
self.tools_context,
|
|
125
|
+
self.eval_fn,
|
|
126
|
+
effective_previous_add_context,
|
|
127
|
+
effective_existing_context,
|
|
128
|
+
)
|
|
129
|
+
effective_existing_context = new_context
|
|
130
|
+
effective_previous_add_context = new_add_context
|
|
131
|
+
tool_result = output
|
|
128
132
|
elif tool_call["name"] == "load_functions": # Handle load_functions separately
|
|
129
133
|
valid_tools, unconnected_links = await get_valid_tools(
|
|
130
134
|
tool_ids=tool_call["args"]["tool_ids"], registry=self.registry
|
|
@@ -138,8 +142,13 @@ class UnifiedAgent(BaseAgent):
|
|
|
138
142
|
ai_msg = f"Please login to the following app(s) using the following links and let me know in order to proceed:\n {links} "
|
|
139
143
|
elif tool_call["name"] == "search_functions":
|
|
140
144
|
tool_result = await meta_tools["search_functions"].ainvoke(tool_call["args"])
|
|
145
|
+
else:
|
|
146
|
+
raise Exception(
|
|
147
|
+
f"Unexpected tool call: {tool_call['name']}. "
|
|
148
|
+
"tool calls must be one of 'enter_playbook_mode', 'execute_ipython_cell', 'load_functions', or 'search_functions'"
|
|
149
|
+
)
|
|
141
150
|
except Exception as e:
|
|
142
|
-
tool_result =
|
|
151
|
+
tool_result = str(e)
|
|
143
152
|
|
|
144
153
|
tool_message = ToolMessage(
|
|
145
154
|
content=json.dumps(tool_result),
|
|
@@ -148,40 +157,30 @@ class UnifiedAgent(BaseAgent):
|
|
|
148
157
|
)
|
|
149
158
|
tool_messages.append(tool_message)
|
|
150
159
|
|
|
160
|
+
if new_tool_ids:
|
|
161
|
+
self.tools_config.extend(new_tool_ids)
|
|
162
|
+
self.exported_tools = await self.registry.export_tools(new_tool_ids, ToolFormat.LANGCHAIN)
|
|
163
|
+
self.final_instructions, self.tools_context = create_default_prompt(
|
|
164
|
+
self.exported_tools, self.additional_tools, self.instructions
|
|
165
|
+
)
|
|
151
166
|
if ask_user:
|
|
152
167
|
tool_messages.append(AIMessage(content=ai_msg))
|
|
153
|
-
return Command(update={"messages": tool_messages, "selected_tool_ids": new_tool_ids})
|
|
154
|
-
|
|
155
|
-
return Command(goto="call_model", update={"messages": tool_messages, "selected_tool_ids": new_tool_ids})
|
|
156
|
-
|
|
157
|
-
def sandbox(state: CodeActState) -> Command[Literal["call_model"]]:
|
|
158
|
-
code = state.get("code")
|
|
159
|
-
|
|
160
|
-
if not code:
|
|
161
|
-
logger.error("Sandbox called without code")
|
|
162
168
|
return Command(
|
|
163
|
-
|
|
164
|
-
|
|
169
|
+
update={
|
|
170
|
+
"messages": tool_messages,
|
|
171
|
+
"selected_tool_ids": new_tool_ids,
|
|
172
|
+
"context": effective_existing_context,
|
|
173
|
+
"add_context": effective_previous_add_context,
|
|
174
|
+
}
|
|
165
175
|
)
|
|
166
176
|
|
|
167
|
-
previous_add_context = state.get("add_context", {})
|
|
168
|
-
add_context = inject_context(previous_add_context, self.tools_context)
|
|
169
|
-
existing_context = state.get("context", {})
|
|
170
|
-
context = {**existing_context, **add_context}
|
|
171
|
-
# Execute the script in the sandbox
|
|
172
|
-
|
|
173
|
-
output, new_context, new_add_context = self.eval_fn(
|
|
174
|
-
code, context, previous_add_context, 180
|
|
175
|
-
) # default timeout 3 min
|
|
176
|
-
output = smart_truncate(output)
|
|
177
|
-
|
|
178
177
|
return Command(
|
|
179
178
|
goto="call_model",
|
|
180
179
|
update={
|
|
181
|
-
"
|
|
182
|
-
"
|
|
183
|
-
"context":
|
|
184
|
-
"add_context":
|
|
180
|
+
"messages": tool_messages,
|
|
181
|
+
"selected_tool_ids": new_tool_ids,
|
|
182
|
+
"context": effective_existing_context,
|
|
183
|
+
"add_context": effective_previous_add_context,
|
|
185
184
|
},
|
|
186
185
|
)
|
|
187
186
|
|
|
@@ -272,16 +271,20 @@ class UnifiedAgent(BaseAgent):
|
|
|
272
271
|
update={"messages": [mock_assistant_message, mock_exit_tool_response], "playbook_mode": "normal"}
|
|
273
272
|
)
|
|
274
273
|
|
|
275
|
-
def route_entry(state: CodeActState) -> Literal["call_model", "playbook"]:
|
|
274
|
+
async def route_entry(state: CodeActState) -> Literal["call_model", "playbook"]:
|
|
276
275
|
"""Route to either normal mode or playbook creation"""
|
|
276
|
+
self.exported_tools = []
|
|
277
|
+
self.tools_config.extend(state.get("selected_tool_ids", []))
|
|
278
|
+
self.exported_tools = await self.registry.export_tools(self.tools_config, ToolFormat.LANGCHAIN)
|
|
279
|
+
self.final_instructions, self.tools_context = create_default_prompt(
|
|
280
|
+
self.exported_tools, self.additional_tools, self.instructions
|
|
281
|
+
)
|
|
277
282
|
if state.get("playbook_mode") in ["planning", "confirming", "generating"]:
|
|
278
283
|
return "playbook"
|
|
279
|
-
|
|
280
284
|
return "call_model"
|
|
281
285
|
|
|
282
286
|
agent = StateGraph(state_schema=CodeActState)
|
|
283
287
|
agent.add_node(call_model, retry_policy=RetryPolicy(max_attempts=3, retry_on=filter_retry_on))
|
|
284
|
-
agent.add_node(sandbox)
|
|
285
288
|
agent.add_node(playbook)
|
|
286
289
|
agent.add_node(execute_tools)
|
|
287
290
|
agent.add_conditional_edges(START, route_entry)
|
|
@@ -4,24 +4,29 @@ from collections.abc import Sequence
|
|
|
4
4
|
|
|
5
5
|
from langchain_core.tools import StructuredTool
|
|
6
6
|
|
|
7
|
+
from universal_mcp.agents.codeact0.utils import schema_to_signature
|
|
8
|
+
|
|
7
9
|
uneditable_prompt = """
|
|
8
10
|
You are **Wingmen**, an AI Assistant created by AgentR — a creative, straight-forward, and direct principal software engineer with access to tools.
|
|
9
11
|
|
|
10
12
|
Your job is to answer the user's question or perform the task they ask for.
|
|
11
|
-
- Answer simple questions (which do not require you to write any code or access any external resources) directly. Note that any operation that involves using ONLY print functions should be answered directly.
|
|
12
|
-
- For
|
|
13
|
-
- You
|
|
14
|
-
-
|
|
13
|
+
- Answer simple questions (which do not require you to write any code or access any external resources) directly. Note that any operation that involves using ONLY print functions should be answered directly in the chat. NEVER write a string yourself and print it.
|
|
14
|
+
- For task requiring operations or access to external resources, you should achieve the task by executing Python code snippets.
|
|
15
|
+
- You have access to `execute_ipython_cell` tool that allows you to execute Python code in an IPython notebook cell.
|
|
16
|
+
- You also have access to two tools for finding and loading more python functions- `search_functions` and `load_functions`, which you must use for finding functions for using different external applications or additional functionality.
|
|
17
|
+
- Prioritize connected applications over unconnected ones from the output of `search_functions`.
|
|
18
|
+
- When multiple apps are connected, or none of the apps are connected, YOU MUST ask the user to choose the application(s). The search results will inform you when such a case occurs, and you must stop and ask the user if multiple apps are relevant.
|
|
19
|
+
- In writing or natural language processing tasks DO NOT answer directly. Instead use `execute_ipython_cell` tool with the AI functions provided to you for tasks like summarizing, text generation, classification, data extraction from text or unstructured data, etc. Avoid hardcoded approaches to classification, data extraction, or creative writing.
|
|
15
20
|
- The code you write will be executed in a sandbox environment, and you can use the output of previous executions in your code. variables, functions, imports are retained.
|
|
16
|
-
- Read and understand the output of the previous code snippet and use it to answer the user's request. Note that the code output is NOT visible to the user, so after the task is complete, you have to give the output to the user in a markdown format.
|
|
17
|
-
- If needed, feel free to ask for more information from the user to clarify the task.
|
|
21
|
+
- Read and understand the output of the previous code snippet and use it to answer the user's request. Note that the code output is NOT visible to the user, so after the task is complete, you have to give the output to the user in a markdown format. Similarly, you should only use print/smart_print for your own analysis, the user does not get the output.
|
|
22
|
+
- If needed, feel free to ask for more information from the user (without using the `execute_ipython_cell` tool) to clarify the task.
|
|
18
23
|
|
|
19
24
|
GUIDELINES for writing code:
|
|
20
25
|
- Variables defined at the top level of previous code snippets can be referenced in your code.
|
|
21
26
|
- External functions which return a dict or list[dict] are ambiguous. Therefore, you MUST explore the structure of the returned data using `smart_print()` statements before using it, printing keys and values. `smart_print` truncates long strings from data, preventing huge output logs.
|
|
22
27
|
- When an operation involves running a fixed set of steps on a list of items, run one run correctly and then use a for loop to run the steps on each item in the list.
|
|
23
28
|
- In a single code snippet, try to achieve as much as possible.
|
|
24
|
-
- You can only import libraries that come pre-installed with Python.
|
|
29
|
+
- You can only import libraries that come pre-installed with Python. However, do consider searching for external functions first, using the search and load tools to access them in the code.
|
|
25
30
|
- For displaying final results to the user, you must present your output in markdown format, including image links, so that they are rendered and displayed to the user. The code output is NOT visible to the user.
|
|
26
31
|
- Call all functions using keyword arguments only, never positional arguments.
|
|
27
32
|
- Async Functions (Critical): Use them only as follows-
|
|
@@ -40,6 +45,37 @@ Rules:
|
|
|
40
45
|
- Never nest asyncio.run() calls
|
|
41
46
|
"""
|
|
42
47
|
|
|
48
|
+
PLAYBOOK_PLANNING_PROMPT = """Now, you are tasked with creating a reusable playbook from the user's previous workflow.
|
|
49
|
+
|
|
50
|
+
TASK: Analyze the conversation history and code execution to create a step-by-step plan for a reusable function. Do not include the searching and loading of tools. Assume that the tools have already been loaded.
|
|
51
|
+
|
|
52
|
+
Your plan should:
|
|
53
|
+
1. Identify the key steps in the workflow
|
|
54
|
+
2. Mark user-specific variables that should become the main playbook function parameters using `variable_name` syntax. Intermediate variables should not be highlighted using ``
|
|
55
|
+
3. Keep the logic generic and reusable
|
|
56
|
+
4. Be clear and concise
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
```
|
|
60
|
+
1. Connect to database using `db_connection_string`
|
|
61
|
+
2. Query user data for `user_id`
|
|
62
|
+
3. Process results and calculate `metric_name`
|
|
63
|
+
4. Send notification to `email_address`
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Now create a plan based on the conversation history. Enclose it between ``` and ```. Ask the user if the plan is okay."""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
PLAYBOOK_CONFIRMING_PROMPT = """Now, you are tasked with confirming the playbook plan. Return True if the user is happy with the plan, False otherwise. Do not say anything else in your response. The user response will be the last message in the chain.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
PLAYBOOK_GENERATING_PROMPT = """Now, you are tasked with generating the playbook function. Return the function in Python code.
|
|
73
|
+
Do not include any other text in your response.
|
|
74
|
+
The function should be a single, complete piece of code that can be executed independently, based on previously executed code snippets that executed correctly.
|
|
75
|
+
The parameters of the function should be the same as the final confirmed playbook plan.
|
|
76
|
+
Do not include anything other than python code in your response
|
|
77
|
+
"""
|
|
78
|
+
|
|
43
79
|
|
|
44
80
|
def make_safe_function_name(name: str) -> str:
|
|
45
81
|
"""Convert a tool name to a valid Python function name."""
|
|
@@ -133,14 +169,28 @@ def indent(text, prefix, predicate=None):
|
|
|
133
169
|
|
|
134
170
|
def create_default_prompt(
|
|
135
171
|
tools: Sequence[StructuredTool],
|
|
172
|
+
additional_tools: Sequence[StructuredTool],
|
|
136
173
|
base_prompt: str | None = None,
|
|
137
174
|
):
|
|
138
175
|
system_prompt = uneditable_prompt.strip() + (
|
|
139
176
|
"\n\nIn addition to the Python Standard Library, you can use the following external functions:\n"
|
|
140
177
|
)
|
|
141
178
|
tools_context = {}
|
|
142
|
-
|
|
143
179
|
for tool in tools:
|
|
180
|
+
if hasattr(tool, "func") and tool.func is not None:
|
|
181
|
+
tool_callable = tool.func
|
|
182
|
+
is_async = False
|
|
183
|
+
elif hasattr(tool, "coroutine") and tool.coroutine is not None:
|
|
184
|
+
tool_callable = tool.coroutine
|
|
185
|
+
is_async = True
|
|
186
|
+
system_prompt += f'''{"async " if is_async else ""}{schema_to_signature(tool.args, tool.name)}:
|
|
187
|
+
"""{tool.description}"""
|
|
188
|
+
...
|
|
189
|
+
'''
|
|
190
|
+
safe_name = make_safe_function_name(tool.name)
|
|
191
|
+
tools_context[safe_name] = tool_callable
|
|
192
|
+
|
|
193
|
+
for tool in additional_tools:
|
|
144
194
|
if hasattr(tool, "func") and tool.func is not None:
|
|
145
195
|
tool_callable = tool.func
|
|
146
196
|
is_async = False
|
|
@@ -158,35 +208,3 @@ def create_default_prompt(
|
|
|
158
208
|
system_prompt += f"Your goal is to perform the following task:\n\n{base_prompt}"
|
|
159
209
|
|
|
160
210
|
return system_prompt, tools_context
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
PLAYBOOK_PLANNING_PROMPT = """Now, you are tasked with creating a reusable playbook from the user's previous workflow.
|
|
164
|
-
|
|
165
|
-
TASK: Analyze the conversation history and code execution to create a step-by-step plan for a reusable function. Do not include the searching and loading of tools. Assume that the tools have already been loaded.
|
|
166
|
-
|
|
167
|
-
Your plan should:
|
|
168
|
-
1. Identify the key steps in the workflow
|
|
169
|
-
2. Mark user-specific variables that should become the main playbook function parameters using `variable_name` syntax. Intermediate variables should not be highlighted using ``
|
|
170
|
-
3. Keep the logic generic and reusable
|
|
171
|
-
4. Be clear and concise
|
|
172
|
-
|
|
173
|
-
Example:
|
|
174
|
-
```
|
|
175
|
-
1. Connect to database using `db_connection_string`
|
|
176
|
-
2. Query user data for `user_id`
|
|
177
|
-
3. Process results and calculate `metric_name`
|
|
178
|
-
4. Send notification to `email_address`
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Now create a plan based on the conversation history. Enclose it between ``` and ```. Ask the user if the plan is okay."""
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
PLAYBOOK_CONFIRMING_PROMPT = """Now, you are tasked with confirming the playbook plan. Return True if the user is happy with the plan, False otherwise. Do not say anything else in your response. The user response will be the last message in the chain.
|
|
185
|
-
"""
|
|
186
|
-
|
|
187
|
-
PLAYBOOK_GENERATING_PROMPT = """Now, you are tasked with generating the playbook function. Return the function in Python code.
|
|
188
|
-
Do not include any other text in your response.
|
|
189
|
-
The function should be a single, complete piece of code that can be executed independently, based on previously executed code snippets that executed correctly.
|
|
190
|
-
The parameters of the function should be the same as the final confirmed playbook plan.
|
|
191
|
-
Do not include anything other than python code in your response
|
|
192
|
-
"""
|
|
@@ -10,7 +10,7 @@ from typing import Any
|
|
|
10
10
|
|
|
11
11
|
from langchain_core.tools import tool
|
|
12
12
|
|
|
13
|
-
from universal_mcp.agents.codeact0.utils import derive_context
|
|
13
|
+
from universal_mcp.agents.codeact0.utils import derive_context, inject_context, smart_truncate
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def eval_unsafe(
|
|
@@ -53,6 +53,14 @@ def eval_unsafe(
|
|
|
53
53
|
if thread.is_alive():
|
|
54
54
|
result_container["output"] = f"Code timeout: code execution exceeded {timeout} seconds."
|
|
55
55
|
|
|
56
|
+
# If NameError for provider__tool occurred, append guidance (no retry)
|
|
57
|
+
try:
|
|
58
|
+
m = re.search(r"NameError:\s*name\s*'([^']+)'\s*is\s*not\s*defined", result_container["output"])
|
|
59
|
+
if m and "__" in m.group(1):
|
|
60
|
+
result_container["output"] += "\nHint: If it is a valid tool, load it before running this snippet."
|
|
61
|
+
except Exception:
|
|
62
|
+
pass
|
|
63
|
+
|
|
56
64
|
# Filter locals for picklable/storable variables
|
|
57
65
|
all_vars = {}
|
|
58
66
|
for key, value in _locals.items():
|
|
@@ -99,3 +107,25 @@ def execute_ipython_cell(snippet: str) -> str:
|
|
|
99
107
|
|
|
100
108
|
# Your actual execution logic would go here
|
|
101
109
|
return f"Successfully executed {len(snippet)} characters of Python code"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
async def handle_execute_ipython_cell(
|
|
113
|
+
code: str,
|
|
114
|
+
tools_context: dict[str, Any],
|
|
115
|
+
eval_fn,
|
|
116
|
+
effective_previous_add_context: dict[str, Any],
|
|
117
|
+
effective_existing_context: dict[str, Any],
|
|
118
|
+
) -> tuple[str, dict[str, Any], dict[str, Any]]:
|
|
119
|
+
"""
|
|
120
|
+
Execute a code cell with shared state, supporting both sync and async eval functions.
|
|
121
|
+
|
|
122
|
+
Returns (output, new_context, new_add_context).
|
|
123
|
+
"""
|
|
124
|
+
add_context = inject_context(effective_previous_add_context, tools_context)
|
|
125
|
+
context = {**effective_existing_context, **add_context}
|
|
126
|
+
if inspect.iscoroutinefunction(eval_fn):
|
|
127
|
+
output, new_context, new_add_context = await eval_fn(code, context, effective_previous_add_context, 180)
|
|
128
|
+
else:
|
|
129
|
+
output, new_context, new_add_context = eval_fn(code, context, effective_previous_add_context, 180)
|
|
130
|
+
output = smart_truncate(output)
|
|
131
|
+
return output, new_context, new_add_context
|
|
@@ -6,6 +6,8 @@ from langgraph.prebuilt.chat_agent_executor import AgentState
|
|
|
6
6
|
def _enqueue(left: list, right: list) -> list:
|
|
7
7
|
"""Treat left as a FIFO queue, append new items from right (preserve order),
|
|
8
8
|
keep items unique, and cap total size to 20 (drop oldest items)."""
|
|
9
|
+
|
|
10
|
+
# Tool ifd are unique
|
|
9
11
|
max_size = 30
|
|
10
12
|
preferred_size = 20
|
|
11
13
|
if len(right) > preferred_size:
|
|
@@ -20,7 +22,7 @@ def _enqueue(left: list, right: list) -> list:
|
|
|
20
22
|
if len(queue) > preferred_size:
|
|
21
23
|
queue = queue[-preferred_size:]
|
|
22
24
|
|
|
23
|
-
return queue
|
|
25
|
+
return list(set(queue))
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class CodeActState(AgentState):
|