universal-mcp-agents 0.1.23rc7__tar.gz → 0.1.24__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.
- universal_mcp_agents-0.1.24/.gemini/settings.json +7 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/PKG-INFO +3 -3
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/pyproject.toml +3 -3
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/__init__.py +1 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/base.py +7 -7
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/cli.py +2 -2
- universal_mcp_agents-0.1.24/src/universal_mcp/agents/codeact0/agent.py +373 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/prompts.py +121 -54
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/sandbox.py +49 -12
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/state.py +8 -0
- universal_mcp_agents-0.1.24/src/universal_mcp/agents/codeact0/tools.py +564 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/utils.py +255 -2
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/llm.py +14 -4
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/sandbox.py +29 -7
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/applications/llm/app.py +81 -27
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/uv.lock +97 -93
- universal_mcp_agents-0.1.23rc7/src/universal_mcp/agents/codeact0/agent.py +0 -465
- universal_mcp_agents-0.1.23rc7/src/universal_mcp/agents/codeact0/tools.py +0 -322
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/.github/workflows/evals.yml +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/.github/workflows/lint.yml +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/.github/workflows/release-please.yml +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/.github/workflows/tests.yml +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/.gitignore +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/.pre-commit-config.yaml +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/GEMINI.md +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/PROMPTS.md +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/README.md +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/bump_and_release.sh +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/__init__.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/dataset.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/datasets/exact.jsonl +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/datasets/tasks.jsonl +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/datasets/test.jsonl +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/evaluators.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/prompts.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/run.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/utils.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/tests/test_agents.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/tests/test_sandbox.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/__init__.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/__main__.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/agent.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/context.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/graph.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/prompts.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/state.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/tools.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/builder/__main__.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/builder/builder.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/builder/helper.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/builder/prompts.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/builder/state.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/__init__.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/__main__.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/config.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/langgraph_agent.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/llm_tool.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/hil.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/react.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/shared/__main__.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/shared/prompts.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/shared/tool_node.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/simple.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/utils.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/applications/filesystem/__init__.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/applications/filesystem/app.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/applications/llm/__init__.py +0 -0
- {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/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.24
|
|
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
|
|
@@ -12,8 +12,8 @@ Requires-Dist: langchain-anthropic>=0.3.19
|
|
|
12
12
|
Requires-Dist: langchain-google-genai>=2.1.10
|
|
13
13
|
Requires-Dist: langchain-openai>=0.3.32
|
|
14
14
|
Requires-Dist: langgraph>=0.6.6
|
|
15
|
-
Requires-Dist: universal-mcp-applications>=0.1.
|
|
16
|
-
Requires-Dist: universal-mcp>=0.1.
|
|
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'
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "universal-mcp-agents"
|
|
9
|
-
version = "0.1.
|
|
9
|
+
version = "0.1.24"
|
|
10
10
|
description = "Add your description here"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [
|
|
@@ -19,8 +19,8 @@ dependencies = [
|
|
|
19
19
|
"langchain-google-genai>=2.1.10",
|
|
20
20
|
"langchain-openai>=0.3.32",
|
|
21
21
|
"langgraph>=0.6.6",
|
|
22
|
-
"universal-mcp>=0.1.
|
|
23
|
-
"universal-mcp-applications>=0.1.
|
|
22
|
+
"universal-mcp>=0.1.24rc29",
|
|
23
|
+
"universal-mcp-applications>=0.1.30",
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
[project.license]
|
{universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/__init__.py
RENAMED
|
@@ -11,6 +11,7 @@ from universal_mcp.agents.simple import SimpleAgent
|
|
|
11
11
|
def get_agent(
|
|
12
12
|
agent_name: Literal["react", "simple", "builder", "bigtool", "codeact-repl"],
|
|
13
13
|
):
|
|
14
|
+
print("agent_name", agent_name)
|
|
14
15
|
if agent_name == "react":
|
|
15
16
|
return ReactAgent
|
|
16
17
|
elif agent_name == "simple":
|
{universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/base.py
RENAMED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
from typing import Any, cast
|
|
2
|
-
from uuid import uuid4
|
|
3
1
|
import asyncio
|
|
2
|
+
from typing import cast
|
|
3
|
+
from uuid import uuid4
|
|
4
4
|
|
|
5
5
|
from langchain_core.messages import AIMessageChunk
|
|
6
6
|
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
7
7
|
from langgraph.graph import StateGraph
|
|
8
8
|
from langgraph.types import Command
|
|
9
9
|
from universal_mcp.logger import logger
|
|
10
|
+
|
|
10
11
|
from .utils import RichCLI
|
|
11
12
|
|
|
12
13
|
|
|
@@ -66,11 +67,10 @@ class BaseAgent:
|
|
|
66
67
|
):
|
|
67
68
|
if event == "messages" and isinstance(meta, (tuple, list)) and len(meta) == 2: # noqa: PLR2004
|
|
68
69
|
payload, meta_dict = meta
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if is_agent_builder and not additional_kwargs.get("stream"):
|
|
70
|
+
metadata = getattr(payload, "metadata", {}) or {}
|
|
71
|
+
if metadata and "quiet" in metadata.get("tags"):
|
|
72
|
+
continue
|
|
73
|
+
if meta_dict.get("tags") and "quiet" in meta_dict.get("tags"):
|
|
74
74
|
continue
|
|
75
75
|
if isinstance(payload, AIMessageChunk):
|
|
76
76
|
last_ai_chunk = payload
|
{universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/cli.py
RENAMED
|
@@ -18,14 +18,14 @@ app = Typer()
|
|
|
18
18
|
mcp client run --config client_config.json
|
|
19
19
|
""",
|
|
20
20
|
)
|
|
21
|
-
def run(name: str = "
|
|
21
|
+
def run(name: str = "codeact-repl"):
|
|
22
22
|
"""Run the agent CLI"""
|
|
23
23
|
|
|
24
24
|
setup_logger(log_file=None, level="ERROR")
|
|
25
25
|
client = AgentrClient()
|
|
26
26
|
params = {
|
|
27
27
|
"instructions": "You are a helpful assistant",
|
|
28
|
-
"model": "
|
|
28
|
+
"model": "anthropic:claude-4-sonnet-20250514",
|
|
29
29
|
"registry": AgentrRegistry(client=client),
|
|
30
30
|
"memory": MemorySaver(),
|
|
31
31
|
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Literal, cast
|
|
6
|
+
|
|
7
|
+
from langchain_anthropic import ChatAnthropic
|
|
8
|
+
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
|
|
9
|
+
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
10
|
+
from langgraph.graph import START, StateGraph
|
|
11
|
+
from langgraph.types import Command, RetryPolicy, StreamWriter
|
|
12
|
+
from universal_mcp.tools.registry import ToolRegistry
|
|
13
|
+
from universal_mcp.types import ToolFormat
|
|
14
|
+
|
|
15
|
+
from universal_mcp.agents.base import BaseAgent
|
|
16
|
+
from universal_mcp.agents.codeact0.llm_tool import smart_print
|
|
17
|
+
from universal_mcp.agents.codeact0.prompts import (
|
|
18
|
+
AGENT_BUILDER_GENERATING_PROMPT,
|
|
19
|
+
AGENT_BUILDER_META_PROMPT,
|
|
20
|
+
AGENT_BUILDER_PLANNING_PROMPT,
|
|
21
|
+
AGENT_BUILDER_PLAN_PATCH_PROMPT,
|
|
22
|
+
AGENT_BUILDER_CODE_PATCH_PROMPT,
|
|
23
|
+
build_tool_definitions,
|
|
24
|
+
create_default_prompt,
|
|
25
|
+
)
|
|
26
|
+
from universal_mcp.agents.codeact0.sandbox import eval_unsafe, execute_ipython_cell, handle_execute_ipython_cell
|
|
27
|
+
from universal_mcp.agents.codeact0.state import AgentBuilderCode, AgentBuilderMeta, AgentBuilderPlan, AgentBuilderPatch, CodeActState
|
|
28
|
+
from universal_mcp.agents.codeact0.tools import (
|
|
29
|
+
create_agent_builder_tools,
|
|
30
|
+
create_meta_tools,
|
|
31
|
+
)
|
|
32
|
+
from universal_mcp.agents.codeact0.utils import build_anthropic_cache_message, extract_plan_parameters, get_connected_apps_string, strip_thinking
|
|
33
|
+
from universal_mcp.agents.codeact0.utils import apply_patch_or_use_proposed
|
|
34
|
+
from universal_mcp.agents.llm import load_chat_model
|
|
35
|
+
from universal_mcp.agents.utils import convert_tool_ids_to_dict, filter_retry_on
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CodeActPlaybookAgent(BaseAgent):
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
name: str,
|
|
42
|
+
instructions: str,
|
|
43
|
+
model: str,
|
|
44
|
+
memory: BaseCheckpointSaver | None = None,
|
|
45
|
+
registry: ToolRegistry | None = None,
|
|
46
|
+
agent_builder_registry: object | None = None,
|
|
47
|
+
sandbox_timeout: int = 20,
|
|
48
|
+
**kwargs,
|
|
49
|
+
):
|
|
50
|
+
super().__init__(
|
|
51
|
+
name=name,
|
|
52
|
+
instructions=instructions,
|
|
53
|
+
model=model,
|
|
54
|
+
memory=memory,
|
|
55
|
+
**kwargs,
|
|
56
|
+
)
|
|
57
|
+
self.model_instance = load_chat_model(model)
|
|
58
|
+
self.agent_builder_model_instance = load_chat_model("anthropic:claude-sonnet-4-5-20250929", thinking=False, disable_streaming = True, tags=("quiet",))
|
|
59
|
+
self.registry = registry
|
|
60
|
+
self.agent_builder_registry = agent_builder_registry
|
|
61
|
+
self.agent = agent_builder_registry.get_agent() if agent_builder_registry else None
|
|
62
|
+
|
|
63
|
+
self.tools_config = self.agent.tools if self.agent else {}
|
|
64
|
+
self.eval_fn = eval_unsafe
|
|
65
|
+
self.sandbox_timeout = sandbox_timeout
|
|
66
|
+
self.default_tools_config = {
|
|
67
|
+
"llm": ["generate_text", "classify_data", "extract_data", "call_llm"],
|
|
68
|
+
}
|
|
69
|
+
self.final_instructions = ""
|
|
70
|
+
self.tools_context = {}
|
|
71
|
+
self.eval_mode = kwargs.get("eval_mode", False)
|
|
72
|
+
|
|
73
|
+
async def _build_graph(self): # noqa: PLR0915
|
|
74
|
+
"""Build the graph for the CodeAct Playbook Agent."""
|
|
75
|
+
meta_tools = create_meta_tools(self.registry)
|
|
76
|
+
agent_builder_tools = create_agent_builder_tools()
|
|
77
|
+
self.additional_tools = [
|
|
78
|
+
smart_print,
|
|
79
|
+
meta_tools["web_search"],
|
|
80
|
+
meta_tools["read_file"],
|
|
81
|
+
meta_tools["save_file"],
|
|
82
|
+
meta_tools["upload_file"],
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
if self.tools_config:
|
|
86
|
+
await self.registry.load_tools(self.tools_config) # Load provided tools
|
|
87
|
+
if self.default_tools_config:
|
|
88
|
+
await self.registry.load_tools(self.default_tools_config) # Load default tools
|
|
89
|
+
|
|
90
|
+
async def call_model(state: CodeActState) -> Command[Literal["execute_tools"]]:
|
|
91
|
+
"""This node now only ever binds the four meta-tools to the LLM."""
|
|
92
|
+
messages = build_anthropic_cache_message(self.final_instructions) + state["messages"]
|
|
93
|
+
agent_facing_tools = [
|
|
94
|
+
execute_ipython_cell,
|
|
95
|
+
agent_builder_tools["plan_agent"],
|
|
96
|
+
agent_builder_tools["code_and_save_agent"],
|
|
97
|
+
meta_tools["search_functions"],
|
|
98
|
+
meta_tools["load_functions"],
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
if isinstance(self.model_instance, ChatAnthropic):
|
|
102
|
+
model_with_tools = self.model_instance.bind_tools(
|
|
103
|
+
tools=agent_facing_tools,
|
|
104
|
+
tool_choice="auto",
|
|
105
|
+
cache_control={"type": "ephemeral", "ttl": "1h"},
|
|
106
|
+
)
|
|
107
|
+
if isinstance(messages[-1].content, str):
|
|
108
|
+
pass
|
|
109
|
+
else:
|
|
110
|
+
last = copy.deepcopy(messages[-1])
|
|
111
|
+
last.content[-1]["cache_control"] = {"type": "ephemeral", "ttl": "5m"}
|
|
112
|
+
messages[-1] = last
|
|
113
|
+
else:
|
|
114
|
+
model_with_tools = self.model_instance.bind_tools(
|
|
115
|
+
tools=agent_facing_tools,
|
|
116
|
+
tool_choice="auto",
|
|
117
|
+
)
|
|
118
|
+
response = cast(AIMessage, await model_with_tools.ainvoke(messages))
|
|
119
|
+
if response.tool_calls:
|
|
120
|
+
return Command(goto="execute_tools", update={"messages": [response]})
|
|
121
|
+
else:
|
|
122
|
+
return Command(update={"messages": [response], "model_with_tools": model_with_tools})
|
|
123
|
+
|
|
124
|
+
async def execute_tools(state: CodeActState, writer: StreamWriter) -> Command[Literal["call_model"]]:
|
|
125
|
+
"""Execute tool calls"""
|
|
126
|
+
last_message = state["messages"][-1]
|
|
127
|
+
tool_calls = last_message.tool_calls if isinstance(last_message, AIMessage) else []
|
|
128
|
+
|
|
129
|
+
tool_messages = []
|
|
130
|
+
new_tool_ids = []
|
|
131
|
+
tool_result = ""
|
|
132
|
+
ask_user = False
|
|
133
|
+
ai_msg = None
|
|
134
|
+
effective_previous_add_context = state.get("add_context", {})
|
|
135
|
+
effective_existing_context = state.get("context", {})
|
|
136
|
+
plan = state.get("plan", None)
|
|
137
|
+
agent_name = state.get("agent_name", None)
|
|
138
|
+
agent_description = state.get("agent_description", None)
|
|
139
|
+
# logging.info(f"Initial new_tool_ids_for_context: {new_tool_ids_for_context}")
|
|
140
|
+
|
|
141
|
+
for tool_call in tool_calls:
|
|
142
|
+
tool_name = tool_call["name"]
|
|
143
|
+
tool_args = tool_call["args"]
|
|
144
|
+
try:
|
|
145
|
+
if tool_name == "execute_ipython_cell":
|
|
146
|
+
code = tool_call["args"]["snippet"]
|
|
147
|
+
output, new_context, new_add_context = await handle_execute_ipython_cell(
|
|
148
|
+
code,
|
|
149
|
+
self.tools_context, # Uses the dynamically updated context
|
|
150
|
+
self.eval_fn,
|
|
151
|
+
effective_previous_add_context,
|
|
152
|
+
effective_existing_context,
|
|
153
|
+
)
|
|
154
|
+
effective_existing_context = new_context
|
|
155
|
+
effective_previous_add_context = new_add_context
|
|
156
|
+
tool_result = output
|
|
157
|
+
elif tool_name == "load_functions":
|
|
158
|
+
# The tool now does all the work of validation and formatting.
|
|
159
|
+
tool_result, new_context_for_sandbox, valid_tools, unconnected_links = await meta_tools[
|
|
160
|
+
"load_functions"
|
|
161
|
+
].ainvoke(tool_args)
|
|
162
|
+
# We still need to update the sandbox context for `execute_ipython_cell`
|
|
163
|
+
new_tool_ids.extend(valid_tools)
|
|
164
|
+
if new_tool_ids:
|
|
165
|
+
self.tools_context.update(new_context_for_sandbox)
|
|
166
|
+
if unconnected_links:
|
|
167
|
+
ask_user = True
|
|
168
|
+
ai_msg = f"Please login to the following app(s) using the following links and let me know in order to proceed:\n {unconnected_links} "
|
|
169
|
+
|
|
170
|
+
elif tool_name == "search_functions":
|
|
171
|
+
tool_result = await meta_tools["search_functions"].ainvoke(tool_args)
|
|
172
|
+
|
|
173
|
+
elif tool_name == "plan_agent":
|
|
174
|
+
plan, tool_result = await self._create_or_update_plan(state=state, writer=writer, plan=plan)
|
|
175
|
+
ask_user = True
|
|
176
|
+
|
|
177
|
+
elif tool_name == "code_and_save_agent":
|
|
178
|
+
tool_result, effective_previous_add_context, agent_name, agent_description = await self._build_or_patch_code(
|
|
179
|
+
state=state,
|
|
180
|
+
writer=writer,
|
|
181
|
+
plan=plan,
|
|
182
|
+
agent_name=agent_name,
|
|
183
|
+
agent_description=agent_description,
|
|
184
|
+
effective_previous_add_context=effective_previous_add_context,
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
raise Exception(
|
|
188
|
+
f"Unexpected tool call: {tool_call['name']}. "
|
|
189
|
+
"tool calls must be one of 'execute_ipython_cell', 'load_functions', 'search_functions', 'plan_agent', or 'code_and_save_agent'. For using functions, call them in code using 'execute_ipython_cell'."
|
|
190
|
+
)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
tool_result = str(e)
|
|
193
|
+
|
|
194
|
+
tool_message = ToolMessage(
|
|
195
|
+
content=json.dumps(tool_result),
|
|
196
|
+
name=tool_call["name"],
|
|
197
|
+
tool_call_id=tool_call["id"],
|
|
198
|
+
)
|
|
199
|
+
tool_messages.append(tool_message)
|
|
200
|
+
|
|
201
|
+
if ask_user:
|
|
202
|
+
if ai_msg:
|
|
203
|
+
tool_messages.append(AIMessage(content=ai_msg))
|
|
204
|
+
return Command(
|
|
205
|
+
update={
|
|
206
|
+
"messages": tool_messages,
|
|
207
|
+
"selected_tool_ids": new_tool_ids,
|
|
208
|
+
"context": effective_existing_context,
|
|
209
|
+
"add_context": effective_previous_add_context,
|
|
210
|
+
"agent_name": agent_name,
|
|
211
|
+
"agent_description": agent_description,
|
|
212
|
+
"plan": plan,
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return Command(
|
|
217
|
+
goto="call_model",
|
|
218
|
+
update={
|
|
219
|
+
"messages": tool_messages,
|
|
220
|
+
"selected_tool_ids": new_tool_ids,
|
|
221
|
+
"context": effective_existing_context,
|
|
222
|
+
"add_context": effective_previous_add_context,
|
|
223
|
+
"agent_name": agent_name,
|
|
224
|
+
"agent_description": agent_description,
|
|
225
|
+
"plan": plan,
|
|
226
|
+
},
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
async def route_entry(state: CodeActState) -> Command[Literal["call_model", "execute_tools"]]:
|
|
230
|
+
"""Route to either normal mode or agent builder creation"""
|
|
231
|
+
pre_tools = await self.registry.export_tools(format=ToolFormat.NATIVE)
|
|
232
|
+
|
|
233
|
+
# Create the initial system prompt and tools_context in one go
|
|
234
|
+
self.final_instructions, self.tools_context = create_default_prompt(
|
|
235
|
+
pre_tools,
|
|
236
|
+
self.additional_tools,
|
|
237
|
+
self.instructions,
|
|
238
|
+
await get_connected_apps_string(self.registry),
|
|
239
|
+
self.agent,
|
|
240
|
+
is_initial_prompt=True,
|
|
241
|
+
)
|
|
242
|
+
self.preloaded_defs, _ = build_tool_definitions(pre_tools)
|
|
243
|
+
self.preloaded_defs = "\n".join(self.preloaded_defs)
|
|
244
|
+
await self.registry.load_tools(state["selected_tool_ids"])
|
|
245
|
+
exported_tools = await self.registry.export_tools(
|
|
246
|
+
state["selected_tool_ids"], ToolFormat.NATIVE
|
|
247
|
+
) # Get definition for only the new tools
|
|
248
|
+
_, loaded_tools_context = build_tool_definitions(exported_tools)
|
|
249
|
+
self.tools_context.update(loaded_tools_context)
|
|
250
|
+
|
|
251
|
+
if (
|
|
252
|
+
len(state["messages"]) == 1 and self.agent
|
|
253
|
+
): # Inject the agent's script function into add_context for execution
|
|
254
|
+
script = self.agent.instructions.get("script")
|
|
255
|
+
add_context = {"functions": [script]}
|
|
256
|
+
return Command(goto="call_model", update={"add_context": add_context})
|
|
257
|
+
return Command(goto="call_model")
|
|
258
|
+
|
|
259
|
+
agent = StateGraph(state_schema=CodeActState)
|
|
260
|
+
agent.add_node(call_model, retry_policy=RetryPolicy(max_attempts=3, retry_on=filter_retry_on))
|
|
261
|
+
agent.add_node(execute_tools)
|
|
262
|
+
agent.add_node(route_entry)
|
|
263
|
+
agent.add_edge(START, "route_entry")
|
|
264
|
+
return agent.compile(checkpointer=self.memory)
|
|
265
|
+
|
|
266
|
+
async def _create_or_update_plan(self, state: "CodeActState", writer: StreamWriter, plan: list[str] | None):
|
|
267
|
+
"""Sub-agent helper: create or patch-update the agent plan and emit UI updates.
|
|
268
|
+
Returns: (plan: list[str], tool_result: str)
|
|
269
|
+
"""
|
|
270
|
+
plan_id = str(uuid.uuid4())
|
|
271
|
+
writer({"type": "custom", id: plan_id, "name": "planning", "data": {"update": bool(plan)}})
|
|
272
|
+
|
|
273
|
+
# Determine existing plan (prefer persisted agent's plan) and base messages
|
|
274
|
+
existing_plan_steps = (self.agent.instructions.get("plan") if self.agent and getattr(self.agent, "instructions", None) else None) or plan
|
|
275
|
+
base = strip_thinking(state["messages"])
|
|
276
|
+
def with_sys(text: str):
|
|
277
|
+
return [{"role": "system", "content": text}] + base
|
|
278
|
+
|
|
279
|
+
if existing_plan_steps:
|
|
280
|
+
current = "\n".join(map(str, existing_plan_steps or []))
|
|
281
|
+
sys_prompt = self.instructions + "\n" + AGENT_BUILDER_PLAN_PATCH_PROMPT + self.preloaded_defs
|
|
282
|
+
msgs = with_sys(sys_prompt) + [HumanMessage(content=f"Current plan (one step per line):\n{current}")]
|
|
283
|
+
patch_model = self.agent_builder_model_instance.with_structured_output(AgentBuilderPatch)
|
|
284
|
+
proposed = cast(AgentBuilderPatch, await patch_model.ainvoke(msgs)).patch
|
|
285
|
+
updated = apply_patch_or_use_proposed(current, proposed)
|
|
286
|
+
plan = [line for line in updated.splitlines() if line.strip()]
|
|
287
|
+
else:
|
|
288
|
+
sys_prompt = self.instructions + AGENT_BUILDER_PLANNING_PROMPT + self.preloaded_defs
|
|
289
|
+
plan_model = self.agent_builder_model_instance.with_structured_output(AgentBuilderPlan)
|
|
290
|
+
plan = cast(AgentBuilderPlan, await plan_model.ainvoke(with_sys(sys_prompt))).steps
|
|
291
|
+
|
|
292
|
+
writer({"type": "custom", id: plan_id, "name": "planning", "data": {"plan": plan}})
|
|
293
|
+
tool_result = {"plan": plan, "update": bool(plan), "message": f"Successfully generated the agent plan."}
|
|
294
|
+
return plan, tool_result
|
|
295
|
+
|
|
296
|
+
async def _build_or_patch_code(
|
|
297
|
+
self,
|
|
298
|
+
state: "CodeActState",
|
|
299
|
+
writer: StreamWriter,
|
|
300
|
+
plan: list[str] | None,
|
|
301
|
+
agent_name: str | None,
|
|
302
|
+
agent_description: str | None,
|
|
303
|
+
effective_previous_add_context: dict,
|
|
304
|
+
):
|
|
305
|
+
"""Sub-agent helper: generate new code or patch existing code, save, and emit UI updates.
|
|
306
|
+
Returns: (tool_result: str, effective_previous_add_context: dict, agent_name: str | None, agent_description: str | None)
|
|
307
|
+
"""
|
|
308
|
+
generation_id = str(uuid.uuid4())
|
|
309
|
+
writer({"type": "custom", "id": generation_id, "name": "generating", "data": {"update": bool(self.agent)}})
|
|
310
|
+
|
|
311
|
+
base = strip_thinking(state["messages"])
|
|
312
|
+
def with_sys(text: str):
|
|
313
|
+
return [{"role": "system", "content": text}] + base
|
|
314
|
+
plan_text = "\n".join(map(str, plan)) if plan else None
|
|
315
|
+
existing_code = self.agent.instructions.get("script") if self.agent and getattr(self.agent, "instructions", None) else None
|
|
316
|
+
|
|
317
|
+
if self.agent:
|
|
318
|
+
agent_name = getattr(self.agent, "name", None)
|
|
319
|
+
agent_description = getattr(self.agent, "description", None)
|
|
320
|
+
|
|
321
|
+
if not agent_name or not agent_description:
|
|
322
|
+
meta_model = self.agent_builder_model_instance.with_structured_output(AgentBuilderMeta)
|
|
323
|
+
meta = cast(AgentBuilderMeta, await meta_model.ainvoke(with_sys(self.instructions + AGENT_BUILDER_META_PROMPT)))
|
|
324
|
+
agent_name, agent_description = meta.name, meta.description
|
|
325
|
+
|
|
326
|
+
writer({"type": "custom", "id": generation_id, "name": "generating", "data": {"update": bool(self.agent), "name": agent_name, "description": agent_description}})
|
|
327
|
+
|
|
328
|
+
if existing_code:
|
|
329
|
+
generating_instructions = self.instructions + AGENT_BUILDER_CODE_PATCH_PROMPT + self.preloaded_defs
|
|
330
|
+
messages = with_sys(generating_instructions)
|
|
331
|
+
if plan_text:
|
|
332
|
+
messages.append(HumanMessage(content=f"Confirmed plan (one step per line):\n{plan_text}"))
|
|
333
|
+
messages.append(HumanMessage(content=f"Current code to update:\n```python\n{existing_code}\n```"))
|
|
334
|
+
patch_model = self.agent_builder_model_instance.with_structured_output(AgentBuilderPatch)
|
|
335
|
+
proposed = cast(AgentBuilderPatch, await patch_model.ainvoke(messages)).patch
|
|
336
|
+
python_code = apply_patch_or_use_proposed(existing_code, proposed)
|
|
337
|
+
else:
|
|
338
|
+
code_model = self.agent_builder_model_instance.with_structured_output(AgentBuilderCode)
|
|
339
|
+
python_code = cast(AgentBuilderCode, await code_model.ainvoke(with_sys(self.instructions + AGENT_BUILDER_GENERATING_PROMPT + self.preloaded_defs))).code
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
if not self.agent_builder_registry:
|
|
343
|
+
raise ValueError("AgentBuilder registry is not configured")
|
|
344
|
+
|
|
345
|
+
plan_params = extract_plan_parameters(state["plan"])
|
|
346
|
+
instructions_payload = {
|
|
347
|
+
"plan": state["plan"],
|
|
348
|
+
"script": python_code,
|
|
349
|
+
"params": plan_params,
|
|
350
|
+
}
|
|
351
|
+
tool_dict = convert_tool_ids_to_dict(state["selected_tool_ids"])
|
|
352
|
+
res = self.agent_builder_registry.upsert_agent(
|
|
353
|
+
name=agent_name,
|
|
354
|
+
description=agent_description,
|
|
355
|
+
instructions=instructions_payload,
|
|
356
|
+
tools=tool_dict,
|
|
357
|
+
)
|
|
358
|
+
writer({"type": "custom", "id": generation_id, "name": "generating", "data": {"id": str(res.id), "update": bool(self.agent), "name": agent_name, "description": agent_description}})
|
|
359
|
+
tool_result = {
|
|
360
|
+
"id": str(res.id),
|
|
361
|
+
"update": bool(self.agent),
|
|
362
|
+
"name": agent_name,
|
|
363
|
+
"description": agent_description,
|
|
364
|
+
"message": f"Successfully saved the agent code and plan.",
|
|
365
|
+
}
|
|
366
|
+
except Exception:
|
|
367
|
+
tool_result = f"Displaying the final saved code:\n\n{python_code}\nFinal Name: {agent_name}\nDescription: {agent_description}"
|
|
368
|
+
|
|
369
|
+
if "functions" not in effective_previous_add_context:
|
|
370
|
+
effective_previous_add_context["functions"] = []
|
|
371
|
+
effective_previous_add_context["functions"].append(python_code)
|
|
372
|
+
|
|
373
|
+
return tool_result, effective_previous_add_context, agent_name, agent_description
|