universal-mcp-agents 0.1.23rc1__tar.gz → 0.1.23rc3__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.23rc1 → universal_mcp_agents-0.1.23rc3}/PKG-INFO +3 -3
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/pyproject.toml +3 -3
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/__init__.py +2 -2
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/base.py +2 -2
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/cli.py +1 -1
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/codeact0/__main__.py +2 -5
- universal_mcp_agents-0.1.23rc3/src/universal_mcp/agents/codeact0/agent.py +406 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/codeact0/prompts.py +58 -43
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/codeact0/state.py +13 -13
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/codeact0/tools.py +41 -8
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/codeact0/utils.py +26 -5
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/uv.lock +3 -3
- universal_mcp_agents-0.1.23rc1/src/universal_mcp/agents/codeact0/agent.py +0 -365
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/.github/workflows/evals.yml +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/.github/workflows/lint.yml +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/.github/workflows/release-please.yml +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/.github/workflows/tests.yml +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/.gitignore +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/.pre-commit-config.yaml +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/GEMINI.md +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/PROMPTS.md +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/README.md +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/bump_and_release.sh +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/evals/__init__.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/evals/dataset.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/evals/datasets/codeact.jsonl +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/evals/datasets/exact.jsonl +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/evals/datasets/tasks.jsonl +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/evals/evaluators.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/evals/prompts.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/evals/run.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/evals/utils.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/tests/test_agents.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/bigtool/__init__.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/bigtool/__main__.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/bigtool/agent.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/bigtool/context.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/bigtool/graph.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/bigtool/prompts.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/bigtool/state.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/bigtool/tools.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/builder/__main__.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/builder/builder.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/builder/helper.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/builder/prompts.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/builder/state.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/codeact0/__init__.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/codeact0/config.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/codeact0/langgraph_agent.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/codeact0/llm_tool.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/codeact0/sandbox.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/hil.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/llm.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/react.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/sandbox.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/shared/__main__.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/shared/prompts.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/shared/tool_node.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/simple.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/utils.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/applications/filesystem/__init__.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/applications/filesystem/app.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/applications/llm/__init__.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/applications/llm/app.py +0 -0
- {universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/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.23rc3
|
|
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-google-genai>=2.1.10
|
|
|
12
12
|
Requires-Dist: langchain-openai>=0.3.32
|
|
13
13
|
Requires-Dist: langgraph>=0.6.6
|
|
14
14
|
Requires-Dist: typer>=0.17.4
|
|
15
|
-
Requires-Dist: universal-mcp-applications>=0.1.
|
|
16
|
-
Requires-Dist: universal-mcp>=0.1.
|
|
15
|
+
Requires-Dist: universal-mcp-applications>=0.1.25
|
|
16
|
+
Requires-Dist: universal-mcp>=0.1.24rc26
|
|
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.23-
|
|
9
|
+
version = "0.1.23-rc3"
|
|
10
10
|
description = "Add your description here"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [
|
|
@@ -19,8 +19,8 @@ dependencies = [
|
|
|
19
19
|
"langchain-openai>=0.3.32",
|
|
20
20
|
"langgraph>=0.6.6",
|
|
21
21
|
"typer>=0.17.4",
|
|
22
|
-
"universal-mcp>=0.1.
|
|
23
|
-
"universal-mcp-applications>=0.1.
|
|
22
|
+
"universal-mcp>=0.1.24rc26",
|
|
23
|
+
"universal-mcp-applications>=0.1.25",
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
[project.license]
|
|
@@ -9,7 +9,7 @@ from universal_mcp.agents.simple import SimpleAgent
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def get_agent(
|
|
12
|
-
agent_name: Literal["react", "simple", "builder", "bigtool", "codeact-
|
|
12
|
+
agent_name: Literal["react", "simple", "builder", "bigtool", "codeact-repl"],
|
|
13
13
|
):
|
|
14
14
|
if agent_name == "react":
|
|
15
15
|
return ReactAgent
|
|
@@ -23,7 +23,7 @@ def get_agent(
|
|
|
23
23
|
return CodeActPlaybookAgent
|
|
24
24
|
else:
|
|
25
25
|
raise ValueError(
|
|
26
|
-
f"Unknown agent: {agent_name}. Possible values: react, simple, builder, bigtool, codeact-
|
|
26
|
+
f"Unknown agent: {agent_name}. Possible values: react, simple, builder, bigtool, codeact-repl"
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
|
{universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/base.py
RENAMED
|
@@ -66,9 +66,9 @@ class BaseAgent:
|
|
|
66
66
|
):
|
|
67
67
|
if event == "messages" and isinstance(meta, (tuple, list)) and len(meta) == 2:
|
|
68
68
|
payload, meta_dict = meta
|
|
69
|
-
|
|
69
|
+
is_agent_builder = isinstance(meta_dict, dict) and meta_dict.get("langgraph_node") == "agent_builder"
|
|
70
70
|
additional_kwargs = getattr(payload, "additional_kwargs", {}) or {}
|
|
71
|
-
if
|
|
71
|
+
if is_agent_builder and not additional_kwargs.get("stream"):
|
|
72
72
|
continue
|
|
73
73
|
if isinstance(payload, AIMessageChunk):
|
|
74
74
|
last_ai_chunk = payload
|
{universal_mcp_agents-0.1.23rc1 → universal_mcp_agents-0.1.23rc3}/src/universal_mcp/agents/cli.py
RENAMED
|
@@ -25,7 +25,7 @@ def run(name: str = "react"):
|
|
|
25
25
|
client = AgentrClient()
|
|
26
26
|
params = {
|
|
27
27
|
"instructions": "You are a helpful assistant",
|
|
28
|
-
"model": "
|
|
28
|
+
"model": "azure/gpt-4.1",
|
|
29
29
|
"registry": AgentrRegistry(client=client),
|
|
30
30
|
"memory": MemorySaver(),
|
|
31
31
|
}
|
|
@@ -13,15 +13,12 @@ async def main():
|
|
|
13
13
|
agent = CodeActPlaybookAgent(
|
|
14
14
|
name="CodeAct Agent",
|
|
15
15
|
instructions="Be very concise in your answers.",
|
|
16
|
-
model="
|
|
17
|
-
tools={"google_mail": ["list_messages"]},
|
|
16
|
+
model="azure/gpt-4.1",
|
|
18
17
|
registry=AgentrRegistry(),
|
|
19
18
|
memory=memory,
|
|
20
19
|
)
|
|
21
20
|
print("Starting agent...")
|
|
22
|
-
result = await agent.invoke(
|
|
23
|
-
user_input="Fetch unsubscribe links from my Gmail inbox for promo emails I have received in the last 7 days"
|
|
24
|
-
)
|
|
21
|
+
result = await agent.invoke(user_input="load all the tools of reddit which can be used to search subreddit")
|
|
25
22
|
print(messages_to_list(result["messages"]))
|
|
26
23
|
|
|
27
24
|
|
|
@@ -0,0 +1,406 @@
|
|
|
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, ToolMessage
|
|
9
|
+
from langchain_core.tools import StructuredTool
|
|
10
|
+
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
11
|
+
from langgraph.graph import START, StateGraph
|
|
12
|
+
from langgraph.types import Command, RetryPolicy, StreamWriter
|
|
13
|
+
from universal_mcp.tools.registry import ToolRegistry
|
|
14
|
+
from universal_mcp.types import ToolFormat
|
|
15
|
+
|
|
16
|
+
from universal_mcp.agents.base import BaseAgent
|
|
17
|
+
from universal_mcp.agents.codeact0.llm_tool import smart_print
|
|
18
|
+
from universal_mcp.agents.codeact0.prompts import (
|
|
19
|
+
AGENT_BUILDER_GENERATING_PROMPT,
|
|
20
|
+
AGENT_BUILDER_META_PROMPT,
|
|
21
|
+
AGENT_BUILDER_PLANNING_PROMPT,
|
|
22
|
+
create_default_prompt,
|
|
23
|
+
)
|
|
24
|
+
from universal_mcp.agents.codeact0.sandbox import eval_unsafe, execute_ipython_cell, handle_execute_ipython_cell
|
|
25
|
+
from universal_mcp.agents.codeact0.state import CodeActState, AgentBuilderCode, AgentBuilderMeta, AgentBuilderPlan
|
|
26
|
+
from universal_mcp.agents.codeact0.tools import (
|
|
27
|
+
create_meta_tools,
|
|
28
|
+
enter_agent_builder_mode,
|
|
29
|
+
get_valid_tools,
|
|
30
|
+
)
|
|
31
|
+
from universal_mcp.agents.codeact0.utils import build_anthropic_cache_message, get_connected_apps_string
|
|
32
|
+
from universal_mcp.agents.llm import load_chat_model
|
|
33
|
+
from universal_mcp.agents.utils import convert_tool_ids_to_dict, filter_retry_on, get_message_text
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CodeActPlaybookAgent(BaseAgent):
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
name: str,
|
|
40
|
+
instructions: str,
|
|
41
|
+
model: str,
|
|
42
|
+
memory: BaseCheckpointSaver | None = None,
|
|
43
|
+
registry: ToolRegistry | None = None,
|
|
44
|
+
agent_builder_registry: object | None = None,
|
|
45
|
+
sandbox_timeout: int = 20,
|
|
46
|
+
**kwargs,
|
|
47
|
+
):
|
|
48
|
+
super().__init__(
|
|
49
|
+
name=name,
|
|
50
|
+
instructions=instructions,
|
|
51
|
+
model=model,
|
|
52
|
+
memory=memory,
|
|
53
|
+
**kwargs,
|
|
54
|
+
)
|
|
55
|
+
self.model_instance = load_chat_model(model)
|
|
56
|
+
self.agent_builder_model_instance = load_chat_model("azure/gpt-4.1")
|
|
57
|
+
self.registry = registry
|
|
58
|
+
self.agent_builder_registry = agent_builder_registry
|
|
59
|
+
self.agent = agent_builder_registry.get_agent() if agent_builder_registry else None
|
|
60
|
+
self.tools_config = self.agent.tools if self.agent else {}
|
|
61
|
+
self.eval_fn = eval_unsafe
|
|
62
|
+
self.sandbox_timeout = sandbox_timeout
|
|
63
|
+
self.default_tools_config = {
|
|
64
|
+
"llm": ["generate_text", "classify_data", "extract_data", "call_llm"],
|
|
65
|
+
}
|
|
66
|
+
self.final_instructions = ""
|
|
67
|
+
self.tools_context = {}
|
|
68
|
+
self.exported_tools = []
|
|
69
|
+
|
|
70
|
+
async def _build_graph(self):
|
|
71
|
+
meta_tools = create_meta_tools(self.registry)
|
|
72
|
+
additional_tools = [smart_print, meta_tools["web_search"]]
|
|
73
|
+
self.additional_tools = [
|
|
74
|
+
t if isinstance(t, StructuredTool) else StructuredTool.from_function(t) for t in additional_tools
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
if self.tools_config:
|
|
78
|
+
if isinstance(self.tools_config, dict):
|
|
79
|
+
self.tools_config = [
|
|
80
|
+
f"{provider}__{tool}" for provider, tools in self.tools_config.items() for tool in tools
|
|
81
|
+
]
|
|
82
|
+
if not self.registry:
|
|
83
|
+
raise ValueError("Tools are configured but no registry is provided")
|
|
84
|
+
await self.registry.export_tools(self.tools_config, ToolFormat.LANGCHAIN)
|
|
85
|
+
|
|
86
|
+
await self.registry.export_tools(self.default_tools_config, ToolFormat.LANGCHAIN)
|
|
87
|
+
|
|
88
|
+
async def call_model(state: CodeActState) -> Command[Literal["execute_tools"]]:
|
|
89
|
+
"""This node now only ever binds the four meta-tools to the LLM."""
|
|
90
|
+
messages = build_anthropic_cache_message(self.final_instructions) + state["messages"]
|
|
91
|
+
|
|
92
|
+
agent_facing_tools = [
|
|
93
|
+
execute_ipython_cell,
|
|
94
|
+
enter_agent_builder_mode,
|
|
95
|
+
meta_tools["search_functions"],
|
|
96
|
+
meta_tools["load_functions"],
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
if isinstance(self.model_instance, ChatAnthropic):
|
|
100
|
+
model_with_tools = self.model_instance.bind_tools(
|
|
101
|
+
tools=agent_facing_tools,
|
|
102
|
+
tool_choice="auto",
|
|
103
|
+
cache_control={"type": "ephemeral", "ttl": "1h"},
|
|
104
|
+
)
|
|
105
|
+
if isinstance(messages[-1].content, str):
|
|
106
|
+
pass
|
|
107
|
+
else:
|
|
108
|
+
last = copy.deepcopy(messages[-1])
|
|
109
|
+
last.content[-1]["cache_control"] = {"type": "ephemeral", "ttl": "5m"}
|
|
110
|
+
messages[-1] = last
|
|
111
|
+
else:
|
|
112
|
+
model_with_tools = self.model_instance.bind_tools(
|
|
113
|
+
tools=agent_facing_tools,
|
|
114
|
+
tool_choice="auto",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
response = cast(AIMessage, model_with_tools.invoke(messages))
|
|
118
|
+
if response.tool_calls:
|
|
119
|
+
return Command(goto="execute_tools", update={"messages": [response]})
|
|
120
|
+
else:
|
|
121
|
+
return Command(update={"messages": [response], "model_with_tools": model_with_tools})
|
|
122
|
+
|
|
123
|
+
async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "agent_builder"]]:
|
|
124
|
+
"""Execute tool calls"""
|
|
125
|
+
last_message = state["messages"][-1]
|
|
126
|
+
tool_calls = last_message.tool_calls if isinstance(last_message, AIMessage) else []
|
|
127
|
+
|
|
128
|
+
tool_messages = []
|
|
129
|
+
new_tool_ids = []
|
|
130
|
+
tool_result = ""
|
|
131
|
+
ask_user = False
|
|
132
|
+
ai_msg = ""
|
|
133
|
+
effective_previous_add_context = state.get("add_context", {})
|
|
134
|
+
effective_existing_context = state.get("context", {})
|
|
135
|
+
# logging.info(f"Initial new_tool_ids_for_context: {new_tool_ids_for_context}")
|
|
136
|
+
|
|
137
|
+
for tool_call in tool_calls:
|
|
138
|
+
tool_name = tool_call["name"]
|
|
139
|
+
tool_args = tool_call["args"]
|
|
140
|
+
try:
|
|
141
|
+
if tool_name == "enter_agent_builder_mode":
|
|
142
|
+
tool_message = ToolMessage(
|
|
143
|
+
content=json.dumps("Entered Agent Builder Mode."),
|
|
144
|
+
name=tool_call["name"],
|
|
145
|
+
tool_call_id=tool_call["id"],
|
|
146
|
+
)
|
|
147
|
+
return Command(
|
|
148
|
+
goto="agent_builder",
|
|
149
|
+
update={"agent_builder_mode": "planning", "messages": [tool_message]}, # Entered Agent Builder mode
|
|
150
|
+
)
|
|
151
|
+
elif tool_name == "execute_ipython_cell":
|
|
152
|
+
code = tool_call["args"]["snippet"]
|
|
153
|
+
output, new_context, new_add_context = await handle_execute_ipython_cell(
|
|
154
|
+
code,
|
|
155
|
+
self.tools_context, # Uses the dynamically updated context
|
|
156
|
+
self.eval_fn,
|
|
157
|
+
effective_previous_add_context,
|
|
158
|
+
effective_existing_context,
|
|
159
|
+
)
|
|
160
|
+
effective_existing_context = new_context
|
|
161
|
+
effective_previous_add_context = new_add_context
|
|
162
|
+
tool_result = output
|
|
163
|
+
elif tool_name == "load_functions":
|
|
164
|
+
# The tool now does all the work of validation and formatting.
|
|
165
|
+
tool_result, new_context_for_sandbox, valid_tools, unconnected_links = await meta_tools["load_functions"].ainvoke(tool_args)
|
|
166
|
+
# We still need to update the sandbox context for `execute_ipython_cell`
|
|
167
|
+
new_tool_ids.extend(valid_tools)
|
|
168
|
+
if new_tool_ids:
|
|
169
|
+
self.tools_context.update(new_context_for_sandbox)
|
|
170
|
+
if unconnected_links:
|
|
171
|
+
ask_user = True
|
|
172
|
+
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} "
|
|
173
|
+
|
|
174
|
+
elif tool_name == "search_functions":
|
|
175
|
+
tool_result = await meta_tools["search_functions"].ainvoke(tool_args)
|
|
176
|
+
else:
|
|
177
|
+
raise Exception(
|
|
178
|
+
f"Unexpected tool call: {tool_call['name']}. "
|
|
179
|
+
"tool calls must be one of 'enter_agent_builder_mode', 'execute_ipython_cell', 'load_functions', or 'search_functions'. For using functions, call them in code using 'execute_ipython_cell'."
|
|
180
|
+
)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
tool_result = str(e)
|
|
183
|
+
|
|
184
|
+
tool_message = ToolMessage(
|
|
185
|
+
content=json.dumps(tool_result),
|
|
186
|
+
name=tool_call["name"],
|
|
187
|
+
tool_call_id=tool_call["id"],
|
|
188
|
+
)
|
|
189
|
+
tool_messages.append(tool_message)
|
|
190
|
+
|
|
191
|
+
if ask_user:
|
|
192
|
+
tool_messages.append(AIMessage(content=ai_msg))
|
|
193
|
+
return Command(
|
|
194
|
+
update={
|
|
195
|
+
"messages": tool_messages,
|
|
196
|
+
"selected_tool_ids": new_tool_ids,
|
|
197
|
+
"context": effective_existing_context,
|
|
198
|
+
"add_context": effective_previous_add_context,
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return Command(
|
|
203
|
+
goto="call_model",
|
|
204
|
+
update={
|
|
205
|
+
"messages": tool_messages,
|
|
206
|
+
"selected_tool_ids": new_tool_ids,
|
|
207
|
+
"context": effective_existing_context,
|
|
208
|
+
"add_context": effective_previous_add_context,
|
|
209
|
+
},
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def agent_builder(state: CodeActState, writer: StreamWriter) -> Command[Literal["call_model"]]:
|
|
213
|
+
agent_builder_mode = state.get("agent_builder_mode")
|
|
214
|
+
if agent_builder_mode == "planning":
|
|
215
|
+
plan_id = str(uuid.uuid4())
|
|
216
|
+
writer({"type": "custom", id: plan_id, "name": "planning", "data": {"update": bool(self.agent)}})
|
|
217
|
+
planning_instructions = self.instructions + AGENT_BUILDER_PLANNING_PROMPT
|
|
218
|
+
messages = [{"role": "system", "content": planning_instructions}] + state["messages"]
|
|
219
|
+
|
|
220
|
+
model_with_structured_output = self.agent_builder_model_instance.with_structured_output(AgentBuilderPlan)
|
|
221
|
+
response = model_with_structured_output.invoke(messages)
|
|
222
|
+
plan = cast(AgentBuilderPlan, response)
|
|
223
|
+
|
|
224
|
+
writer({"type": "custom", id: plan_id, "name": "planning", "data": {"plan": plan.steps}})
|
|
225
|
+
return Command(
|
|
226
|
+
update={
|
|
227
|
+
"messages": [
|
|
228
|
+
AIMessage(
|
|
229
|
+
content=json.dumps(plan.model_dump()),
|
|
230
|
+
additional_kwargs={
|
|
231
|
+
"type": "planning",
|
|
232
|
+
"plan": plan.steps,
|
|
233
|
+
"update": bool(self.agent),
|
|
234
|
+
},
|
|
235
|
+
)
|
|
236
|
+
],
|
|
237
|
+
"agent_builder_mode": "confirming",
|
|
238
|
+
"plan": plan.steps,
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
elif agent_builder_mode == "confirming":
|
|
243
|
+
# Deterministic routing based on three exact button inputs from UI
|
|
244
|
+
user_text = ""
|
|
245
|
+
for m in reversed(state["messages"]):
|
|
246
|
+
try:
|
|
247
|
+
if getattr(m, "type", "") in {"human", "user"}:
|
|
248
|
+
user_text = (get_message_text(m) or "").strip()
|
|
249
|
+
if user_text:
|
|
250
|
+
break
|
|
251
|
+
except Exception:
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
t = user_text.lower()
|
|
255
|
+
if t == "yes, this is great":
|
|
256
|
+
self.meta_id = str(uuid.uuid4())
|
|
257
|
+
name, description = None, None
|
|
258
|
+
if self.agent:
|
|
259
|
+
# Update flow: use existing name/description and do not re-generate
|
|
260
|
+
name = getattr(self.agent, "name", None)
|
|
261
|
+
description = getattr(self.agent, "description", None)
|
|
262
|
+
writer(
|
|
263
|
+
{
|
|
264
|
+
"type": "custom",
|
|
265
|
+
id: self.meta_id,
|
|
266
|
+
"name": "generating",
|
|
267
|
+
"data": {
|
|
268
|
+
"update": True,
|
|
269
|
+
"name": name,
|
|
270
|
+
"description": description,
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
)
|
|
274
|
+
else:
|
|
275
|
+
writer({"type": "custom", id: self.meta_id, "name": "generating", "data": {"update": False}})
|
|
276
|
+
|
|
277
|
+
meta_instructions = self.instructions + AGENT_BUILDER_META_PROMPT
|
|
278
|
+
messages = [{"role": "system", "content": meta_instructions}] + state["messages"]
|
|
279
|
+
|
|
280
|
+
model_with_structured_output = self.agent_builder_model_instance.with_structured_output(AgentBuilderMeta)
|
|
281
|
+
meta_response = model_with_structured_output.invoke(messages)
|
|
282
|
+
meta = cast(AgentBuilderMeta, meta_response)
|
|
283
|
+
name, description = meta.name, meta.description
|
|
284
|
+
|
|
285
|
+
# Emit intermediary UI update with created name/description
|
|
286
|
+
writer(
|
|
287
|
+
{
|
|
288
|
+
"type": "custom",
|
|
289
|
+
id: self.meta_id,
|
|
290
|
+
"name": "generating",
|
|
291
|
+
"data": {"update": False, "name": name, "description": description},
|
|
292
|
+
}
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
return Command(
|
|
296
|
+
goto="agent_builder",
|
|
297
|
+
update={
|
|
298
|
+
"agent_builder_mode": "generating",
|
|
299
|
+
"agent_name": name,
|
|
300
|
+
"agent_description": description,
|
|
301
|
+
},
|
|
302
|
+
)
|
|
303
|
+
if t == "i would like to modify the plan":
|
|
304
|
+
prompt_ai = AIMessage(
|
|
305
|
+
content="What would you like to change about the plan? Let me know and I'll update the plan accordingly.",
|
|
306
|
+
additional_kwargs={"stream": "true"},
|
|
307
|
+
)
|
|
308
|
+
return Command(update={"agent_builder_mode": "planning", "messages": [prompt_ai]})
|
|
309
|
+
if t == "let's do something else":
|
|
310
|
+
return Command(goto="call_model", update={"agent_builder_mode": "inactive"})
|
|
311
|
+
|
|
312
|
+
# Fallback safe default
|
|
313
|
+
return Command(goto="call_model", update={"agent_builder_mode": "inactive"})
|
|
314
|
+
|
|
315
|
+
elif agent_builder_mode == "generating":
|
|
316
|
+
generating_instructions = self.instructions + AGENT_BUILDER_GENERATING_PROMPT
|
|
317
|
+
messages = [{"role": "system", "content": generating_instructions}] + state["messages"]
|
|
318
|
+
|
|
319
|
+
model_with_structured_output = self.agent_builder_model_instance.with_structured_output(AgentBuilderCode)
|
|
320
|
+
response = model_with_structured_output.invoke(messages)
|
|
321
|
+
func_code = cast(AgentBuilderCode, response).code
|
|
322
|
+
|
|
323
|
+
# Extract function name (handle both regular and async functions)
|
|
324
|
+
match = re.search(r"^\s*(?:async\s+)?def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(", func_code, re.MULTILINE)
|
|
325
|
+
if match:
|
|
326
|
+
function_name = match.group(1)
|
|
327
|
+
else:
|
|
328
|
+
function_name = "generated_agent"
|
|
329
|
+
|
|
330
|
+
# Use generated metadata if available
|
|
331
|
+
final_name = state.get("agent_name") or function_name
|
|
332
|
+
final_description = state.get("agent_description") or f"Generated agent: {function_name}"
|
|
333
|
+
|
|
334
|
+
# Save or update an Agent using the helper registry
|
|
335
|
+
try:
|
|
336
|
+
if not self.agent_builder_registry:
|
|
337
|
+
raise ValueError("AgentBuilder registry is not configured")
|
|
338
|
+
|
|
339
|
+
# Build instructions payload embedding the plan and function code
|
|
340
|
+
instructions_payload = {
|
|
341
|
+
"plan": state["plan"],
|
|
342
|
+
"script": func_code,
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
# Convert tool ids list to dict
|
|
346
|
+
tool_dict = convert_tool_ids_to_dict(state["selected_tool_ids"])
|
|
347
|
+
|
|
348
|
+
res = self.agent_builder_registry.upsert_agent(
|
|
349
|
+
name=final_name,
|
|
350
|
+
description=final_description,
|
|
351
|
+
instructions=instructions_payload,
|
|
352
|
+
tools=tool_dict,
|
|
353
|
+
)
|
|
354
|
+
except Exception as e:
|
|
355
|
+
raise e
|
|
356
|
+
|
|
357
|
+
writer(
|
|
358
|
+
{
|
|
359
|
+
"type": "custom",
|
|
360
|
+
id: self.meta_id,
|
|
361
|
+
"name": "generating",
|
|
362
|
+
"data": {
|
|
363
|
+
"id": str(res.id),
|
|
364
|
+
"update": bool(self.agent),
|
|
365
|
+
"name": final_name,
|
|
366
|
+
"description": final_description,
|
|
367
|
+
},
|
|
368
|
+
}
|
|
369
|
+
)
|
|
370
|
+
mock_assistant_message = AIMessage(
|
|
371
|
+
content=json.dumps(response.model_dump()),
|
|
372
|
+
additional_kwargs={
|
|
373
|
+
"type": "generating",
|
|
374
|
+
"id": str(res.id),
|
|
375
|
+
"update": bool(self.agent),
|
|
376
|
+
"name": final_name,
|
|
377
|
+
"description": final_description,
|
|
378
|
+
},
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
return Command(update={"messages": [mock_assistant_message], "agent_builder_mode": "normal"})
|
|
382
|
+
|
|
383
|
+
async def route_entry(state: CodeActState) -> Literal["call_model", "agent_builder"]:
|
|
384
|
+
"""Route to either normal mode or agent builder creation"""
|
|
385
|
+
all_tools = await self.registry.export_tools(state["selected_tool_ids"], ToolFormat.LANGCHAIN)
|
|
386
|
+
# print(all_tools)
|
|
387
|
+
|
|
388
|
+
# Create the initial system prompt and tools_context in one go
|
|
389
|
+
self.final_instructions, self.tools_context = create_default_prompt(
|
|
390
|
+
all_tools,
|
|
391
|
+
self.additional_tools,
|
|
392
|
+
self.instructions,
|
|
393
|
+
await get_connected_apps_string(self.registry),
|
|
394
|
+
self.agent,
|
|
395
|
+
is_initial_prompt=True,
|
|
396
|
+
)
|
|
397
|
+
if state.get("agent_builder_mode") in ["planning", "confirming", "generating"]:
|
|
398
|
+
return "agent_builder"
|
|
399
|
+
return "call_model"
|
|
400
|
+
|
|
401
|
+
agent = StateGraph(state_schema=CodeActState)
|
|
402
|
+
agent.add_node(call_model, retry_policy=RetryPolicy(max_attempts=3, retry_on=filter_retry_on))
|
|
403
|
+
agent.add_node(agent_builder)
|
|
404
|
+
agent.add_node(execute_tools)
|
|
405
|
+
agent.add_conditional_edges(START, route_entry)
|
|
406
|
+
return agent.compile(checkpointer=self.memory)
|