universal-mcp-agents 0.1.19rc1__py3-none-any.whl → 0.1.24rc3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- universal_mcp/agents/__init__.py +15 -16
- universal_mcp/agents/base.py +46 -35
- universal_mcp/agents/bigtool/state.py +1 -1
- universal_mcp/agents/cli.py +2 -5
- universal_mcp/agents/codeact0/__init__.py +2 -3
- universal_mcp/agents/codeact0/__main__.py +4 -7
- universal_mcp/agents/codeact0/agent.py +444 -96
- universal_mcp/agents/codeact0/langgraph_agent.py +1 -1
- universal_mcp/agents/codeact0/llm_tool.py +2 -254
- universal_mcp/agents/codeact0/prompts.py +247 -137
- universal_mcp/agents/codeact0/sandbox.py +52 -18
- universal_mcp/agents/codeact0/state.py +26 -6
- universal_mcp/agents/codeact0/tools.py +400 -74
- universal_mcp/agents/codeact0/utils.py +175 -11
- universal_mcp/agents/codeact00/__init__.py +3 -0
- universal_mcp/agents/{unified → codeact00}/__main__.py +4 -6
- universal_mcp/agents/codeact00/agent.py +578 -0
- universal_mcp/agents/codeact00/config.py +77 -0
- universal_mcp/agents/{unified → codeact00}/langgraph_agent.py +2 -2
- universal_mcp/agents/{unified → codeact00}/llm_tool.py +1 -1
- universal_mcp/agents/codeact00/prompts.py +364 -0
- universal_mcp/agents/{unified → codeact00}/sandbox.py +52 -18
- universal_mcp/agents/codeact00/state.py +66 -0
- universal_mcp/agents/codeact00/tools.py +525 -0
- universal_mcp/agents/codeact00/utils.py +678 -0
- universal_mcp/agents/codeact01/__init__.py +3 -0
- universal_mcp/agents/{codeact → codeact01}/__main__.py +4 -11
- universal_mcp/agents/codeact01/agent.py +413 -0
- universal_mcp/agents/codeact01/config.py +77 -0
- universal_mcp/agents/codeact01/langgraph_agent.py +14 -0
- universal_mcp/agents/codeact01/llm_tool.py +25 -0
- universal_mcp/agents/codeact01/prompts.py +246 -0
- universal_mcp/agents/codeact01/sandbox.py +162 -0
- universal_mcp/agents/{unified → codeact01}/state.py +26 -10
- universal_mcp/agents/codeact01/tools.py +648 -0
- universal_mcp/agents/{unified → codeact01}/utils.py +175 -11
- universal_mcp/agents/llm.py +14 -4
- universal_mcp/agents/react.py +3 -3
- universal_mcp/agents/sandbox.py +124 -69
- universal_mcp/applications/llm/app.py +76 -24
- {universal_mcp_agents-0.1.19rc1.dist-info → universal_mcp_agents-0.1.24rc3.dist-info}/METADATA +6 -5
- universal_mcp_agents-0.1.24rc3.dist-info/RECORD +66 -0
- universal_mcp/agents/codeact/__init__.py +0 -3
- universal_mcp/agents/codeact/agent.py +0 -240
- universal_mcp/agents/codeact/models.py +0 -11
- universal_mcp/agents/codeact/prompts.py +0 -82
- universal_mcp/agents/codeact/sandbox.py +0 -85
- universal_mcp/agents/codeact/state.py +0 -11
- universal_mcp/agents/codeact/utils.py +0 -68
- universal_mcp/agents/codeact0/playbook_agent.py +0 -355
- universal_mcp/agents/unified/README.md +0 -45
- universal_mcp/agents/unified/__init__.py +0 -3
- universal_mcp/agents/unified/agent.py +0 -289
- universal_mcp/agents/unified/prompts.py +0 -192
- universal_mcp/agents/unified/tools.py +0 -188
- universal_mcp_agents-0.1.19rc1.dist-info/RECORD +0 -64
- {universal_mcp_agents-0.1.19rc1.dist-info → universal_mcp_agents-0.1.24rc3.dist-info}/WHEEL +0 -0
|
@@ -4,28 +4,21 @@ 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.
|
|
7
|
+
from universal_mcp.agents.codeact01.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
|
-
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
|
-
|
|
23
|
-
# await agent.run_interactive()
|
|
24
|
-
# async for event in agent.stream(
|
|
25
|
-
# user_input="Fetch unsubscribe links from my Gmail inbox for promo emails I have received in the last 7 days"
|
|
26
|
-
# ):
|
|
27
|
-
# print(event.content, end="")
|
|
28
|
-
result = await agent.invoke(user_input="Get the 50th fibonacci number")
|
|
21
|
+
result = await agent.invoke(user_input="Check my google calendar and show my todays agenda")
|
|
29
22
|
print(messages_to_list(result["messages"]))
|
|
30
23
|
|
|
31
24
|
|
|
@@ -0,0 +1,413 @@
|
|
|
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.codeact01.llm_tool import smart_print
|
|
17
|
+
from universal_mcp.agents.codeact01.prompts import (
|
|
18
|
+
AGENT_BUILDER_GENERATING_PROMPT,
|
|
19
|
+
AGENT_BUILDER_META_PROMPT,
|
|
20
|
+
AGENT_BUILDER_PLANNING_PROMPT,
|
|
21
|
+
build_tool_definitions,
|
|
22
|
+
create_default_prompt,
|
|
23
|
+
)
|
|
24
|
+
from universal_mcp.agents.codeact01.sandbox import eval_unsafe, execute_ipython_cell, handle_execute_ipython_cell
|
|
25
|
+
from universal_mcp.agents.codeact01.state import AgentBuilderCode, AgentBuilderMeta, AgentBuilderPlan, CodeActState
|
|
26
|
+
from universal_mcp.agents.codeact01.tools import (
|
|
27
|
+
create_agent_builder_tools,
|
|
28
|
+
create_meta_tools,
|
|
29
|
+
enter_agent_builder_mode,
|
|
30
|
+
)
|
|
31
|
+
from universal_mcp.agents.codeact01.utils import build_anthropic_cache_message, extract_plan_parameters, get_connected_apps_string, strip_thinking
|
|
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("anthropic:claude-sonnet-4-5-20250929", thinking=False)
|
|
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
|
+
|
|
61
|
+
self.tools_config = self.agent.tools if self.agent else {}
|
|
62
|
+
self.eval_fn = eval_unsafe
|
|
63
|
+
self.sandbox_timeout = sandbox_timeout
|
|
64
|
+
self.default_tools_config = {
|
|
65
|
+
"llm": ["generate_text", "classify_data", "extract_data", "call_llm"],
|
|
66
|
+
}
|
|
67
|
+
self.final_instructions = ""
|
|
68
|
+
self.tools_context = {}
|
|
69
|
+
self.eval_mode = kwargs.get("eval_mode", False)
|
|
70
|
+
|
|
71
|
+
async def handle_agent_save(
|
|
72
|
+
self,
|
|
73
|
+
meta_id: str,
|
|
74
|
+
agent_name: str,
|
|
75
|
+
agent_description: str,
|
|
76
|
+
python_code: str,
|
|
77
|
+
writer: StreamWriter,
|
|
78
|
+
state: CodeActState,
|
|
79
|
+
effective_previous_add_context: dict,
|
|
80
|
+
) -> tuple[str, dict]:
|
|
81
|
+
"""Handle saving agent code and updating the UI."""
|
|
82
|
+
if self.agent:
|
|
83
|
+
# Update flow: use existing name/description and do not re-generate
|
|
84
|
+
agent_name = getattr(self.agent, "name", None)
|
|
85
|
+
agent_description = getattr(self.agent, "description", None)
|
|
86
|
+
writer(
|
|
87
|
+
{
|
|
88
|
+
"type": "custom",
|
|
89
|
+
id: meta_id,
|
|
90
|
+
"name": "generating",
|
|
91
|
+
"data": {
|
|
92
|
+
"update": True,
|
|
93
|
+
"name": agent_name,
|
|
94
|
+
"description": agent_description,
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
tool_result = "Successfully updated the existing agent."
|
|
99
|
+
else:
|
|
100
|
+
# Emit intermediary UI update with created name/description
|
|
101
|
+
writer(
|
|
102
|
+
{
|
|
103
|
+
"type": "custom",
|
|
104
|
+
id: meta_id,
|
|
105
|
+
"name": "generating",
|
|
106
|
+
"data": {"update": False, "name": agent_name, "description": agent_description},
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
res_id = str(uuid.uuid4())
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
if not self.agent_builder_registry:
|
|
113
|
+
raise ValueError("AgentBuilder registry is not configured")
|
|
114
|
+
|
|
115
|
+
plan_params = extract_plan_parameters(state["plan"])
|
|
116
|
+
|
|
117
|
+
# Build instructions payload embedding the plan and function code
|
|
118
|
+
instructions_payload = {
|
|
119
|
+
"plan": state["plan"],
|
|
120
|
+
"script": python_code,
|
|
121
|
+
"params": plan_params,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Convert tool ids list to dict
|
|
125
|
+
tool_dict = convert_tool_ids_to_dict(state["selected_tool_ids"])
|
|
126
|
+
|
|
127
|
+
res = self.agent_builder_registry.upsert_agent(
|
|
128
|
+
name=agent_name,
|
|
129
|
+
description=agent_description,
|
|
130
|
+
instructions=instructions_payload,
|
|
131
|
+
tools=tool_dict,
|
|
132
|
+
)
|
|
133
|
+
res_id = str(res.id)
|
|
134
|
+
tool_result = "Succesfully saved the agent plan and code."
|
|
135
|
+
writer(
|
|
136
|
+
{
|
|
137
|
+
"type": "custom",
|
|
138
|
+
id: meta_id,
|
|
139
|
+
"name": "generating",
|
|
140
|
+
"data": {
|
|
141
|
+
"id": str(res.id),
|
|
142
|
+
"update": bool(self.agent),
|
|
143
|
+
"name": agent_name,
|
|
144
|
+
"description": agent_description,
|
|
145
|
+
"add_context": effective_previous_add_context,
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
except Exception:
|
|
150
|
+
# In case of error, add the code to the exit message content
|
|
151
|
+
tool_result = f"Displaying the final saved code:\n\n{python_code}\nFinal Name: {agent_name}\nDescription: {agent_description}"
|
|
152
|
+
|
|
153
|
+
if "functions" not in effective_previous_add_context:
|
|
154
|
+
effective_previous_add_context["functions"] = []
|
|
155
|
+
effective_previous_add_context["functions"].append(python_code)
|
|
156
|
+
|
|
157
|
+
return tool_result, effective_previous_add_context, res_id
|
|
158
|
+
|
|
159
|
+
async def _build_graph(self): # noqa: PLR0915
|
|
160
|
+
"""Build the graph for the CodeAct Playbook Agent."""
|
|
161
|
+
meta_tools = create_meta_tools(self.registry)
|
|
162
|
+
agent_builder_tools = create_agent_builder_tools()
|
|
163
|
+
self.additional_tools = [
|
|
164
|
+
smart_print,
|
|
165
|
+
meta_tools["web_search"],
|
|
166
|
+
meta_tools["read_file"],
|
|
167
|
+
meta_tools["save_file"],
|
|
168
|
+
meta_tools["upload_file"],
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
if self.tools_config:
|
|
172
|
+
await self.registry.load_tools(self.tools_config) # Load provided tools
|
|
173
|
+
if self.default_tools_config:
|
|
174
|
+
await self.registry.load_tools(self.default_tools_config) # Load default tools
|
|
175
|
+
|
|
176
|
+
async def call_model(state: CodeActState) -> Command[Literal["execute_tools"]]:
|
|
177
|
+
"""This node now only ever binds the four meta-tools to the LLM."""
|
|
178
|
+
messages = build_anthropic_cache_message(self.final_instructions) + state["messages"]
|
|
179
|
+
agent_facing_tools = [
|
|
180
|
+
execute_ipython_cell,
|
|
181
|
+
# enter_agent_builder_mode,
|
|
182
|
+
agent_builder_tools["create_agent_plan"],
|
|
183
|
+
agent_builder_tools["modify_agent_plan"],
|
|
184
|
+
agent_builder_tools["save_agent_code"],
|
|
185
|
+
meta_tools["search_functions"],
|
|
186
|
+
meta_tools["load_functions"],
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
if isinstance(self.model_instance, ChatAnthropic):
|
|
190
|
+
model_with_tools = self.model_instance.bind_tools(
|
|
191
|
+
tools=agent_facing_tools,
|
|
192
|
+
tool_choice="auto",
|
|
193
|
+
cache_control={"type": "ephemeral", "ttl": "1h"},
|
|
194
|
+
)
|
|
195
|
+
if isinstance(messages[-1].content, str):
|
|
196
|
+
pass
|
|
197
|
+
else:
|
|
198
|
+
last = copy.deepcopy(messages[-1])
|
|
199
|
+
last.content[-1]["cache_control"] = {"type": "ephemeral", "ttl": "5m"}
|
|
200
|
+
messages[-1] = last
|
|
201
|
+
else:
|
|
202
|
+
model_with_tools = self.model_instance.bind_tools(
|
|
203
|
+
tools=agent_facing_tools,
|
|
204
|
+
tool_choice="auto",
|
|
205
|
+
)
|
|
206
|
+
response = cast(AIMessage, await model_with_tools.ainvoke(messages))
|
|
207
|
+
if response.tool_calls:
|
|
208
|
+
return Command(goto="execute_tools", update={"messages": [response]})
|
|
209
|
+
else:
|
|
210
|
+
return Command(update={"messages": [response], "model_with_tools": model_with_tools})
|
|
211
|
+
|
|
212
|
+
async def execute_tools(state: CodeActState, writer: StreamWriter) -> Command[Literal["call_model"]]:
|
|
213
|
+
"""Execute tool calls"""
|
|
214
|
+
last_message = state["messages"][-1]
|
|
215
|
+
tool_calls = last_message.tool_calls if isinstance(last_message, AIMessage) else []
|
|
216
|
+
|
|
217
|
+
tool_messages = []
|
|
218
|
+
new_tool_ids = []
|
|
219
|
+
tool_result = ""
|
|
220
|
+
ask_user = False
|
|
221
|
+
ai_msg = ""
|
|
222
|
+
additional_kwargs = {}
|
|
223
|
+
effective_previous_add_context = state.get("add_context", {})
|
|
224
|
+
effective_existing_context = state.get("context", {})
|
|
225
|
+
plan = state.get("plan", None)
|
|
226
|
+
agent_name = state.get("agent_name", None)
|
|
227
|
+
agent_description = state.get("agent_description", None)
|
|
228
|
+
# logging.info(f"Initial new_tool_ids_for_context: {new_tool_ids_for_context}")
|
|
229
|
+
|
|
230
|
+
for tool_call in tool_calls:
|
|
231
|
+
tool_name = tool_call["name"]
|
|
232
|
+
tool_args = tool_call["args"]
|
|
233
|
+
try:
|
|
234
|
+
if tool_name == "execute_ipython_cell":
|
|
235
|
+
code = tool_call["args"]["snippet"]
|
|
236
|
+
output, new_context, new_add_context = await handle_execute_ipython_cell(
|
|
237
|
+
code,
|
|
238
|
+
self.tools_context, # Uses the dynamically updated context
|
|
239
|
+
self.eval_fn,
|
|
240
|
+
effective_previous_add_context,
|
|
241
|
+
effective_existing_context,
|
|
242
|
+
)
|
|
243
|
+
effective_existing_context = new_context
|
|
244
|
+
effective_previous_add_context = new_add_context
|
|
245
|
+
tool_result = output
|
|
246
|
+
elif tool_name == "load_functions":
|
|
247
|
+
# The tool now does all the work of validation and formatting.
|
|
248
|
+
tool_result, new_context_for_sandbox, valid_tools, unconnected_links = await meta_tools[
|
|
249
|
+
"load_functions"
|
|
250
|
+
].ainvoke(tool_args)
|
|
251
|
+
# We still need to update the sandbox context for `execute_ipython_cell`
|
|
252
|
+
new_tool_ids.extend(valid_tools)
|
|
253
|
+
if new_tool_ids:
|
|
254
|
+
self.tools_context.update(new_context_for_sandbox)
|
|
255
|
+
if unconnected_links:
|
|
256
|
+
ask_user = True
|
|
257
|
+
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} "
|
|
258
|
+
|
|
259
|
+
elif tool_name == "search_functions":
|
|
260
|
+
tool_result = await meta_tools["search_functions"].ainvoke(tool_args)
|
|
261
|
+
|
|
262
|
+
elif tool_name == "create_agent_plan":
|
|
263
|
+
plan_id = str(uuid.uuid4())
|
|
264
|
+
writer({"type": "custom", id: plan_id, "name": "planning", "data": {"update": False}})
|
|
265
|
+
ask_user = True
|
|
266
|
+
tool_result = "The user has been shown the plan. Proceed according to their next message"
|
|
267
|
+
plan = await agent_builder_tools["create_agent_plan"].ainvoke(tool_args)
|
|
268
|
+
writer({"type": "custom", id: plan_id, "name": "planning", "data": {"plan": plan}})
|
|
269
|
+
additional_kwargs = {"type": "planning", "plan": plan, "update": False}
|
|
270
|
+
elif tool_name =="modify_agent_plan":
|
|
271
|
+
if plan is None:
|
|
272
|
+
tool_result = "You must have an existing agent plan or created one using create_agent_plan before calling modify_agent_plan."
|
|
273
|
+
else:
|
|
274
|
+
plan_id = str(uuid.uuid4())
|
|
275
|
+
writer({"type": "custom", id: plan_id, "name": "planning", "data": {"update": True}})
|
|
276
|
+
# Apply modifications to the existing plan per docstring semantics
|
|
277
|
+
modifications = await agent_builder_tools["modify_agent_plan"].ainvoke(tool_args)
|
|
278
|
+
new_plan = []
|
|
279
|
+
old_idx = 0
|
|
280
|
+
total_old = len(plan)
|
|
281
|
+
for step in modifications:
|
|
282
|
+
s = step.strip() if isinstance(step, str) else ""
|
|
283
|
+
if not s:
|
|
284
|
+
continue
|
|
285
|
+
if s == "<nochange>":
|
|
286
|
+
if old_idx < total_old:
|
|
287
|
+
new_plan.append(plan[old_idx])
|
|
288
|
+
old_idx += 1
|
|
289
|
+
continue
|
|
290
|
+
if s == "<delete>":
|
|
291
|
+
old_idx += 1
|
|
292
|
+
continue
|
|
293
|
+
if s.startswith("<new>") and s.endswith("</new>"):
|
|
294
|
+
content = s[len("<new>"):-len("</new>")].strip()
|
|
295
|
+
if content:
|
|
296
|
+
new_plan.append(content)
|
|
297
|
+
continue
|
|
298
|
+
if s.startswith("<modify>") and s.endswith("</modify>"):
|
|
299
|
+
content = s[len("<modify>"):-len("</modify>")].strip()
|
|
300
|
+
if content:
|
|
301
|
+
new_plan.append(content)
|
|
302
|
+
# consume the old step being modified
|
|
303
|
+
old_idx += 1
|
|
304
|
+
continue
|
|
305
|
+
# Ignore unknown directives when modifying
|
|
306
|
+
|
|
307
|
+
# Append any remaining old steps not explicitly addressed by modifications
|
|
308
|
+
if old_idx < total_old:
|
|
309
|
+
new_plan.extend(plan[old_idx:])
|
|
310
|
+
|
|
311
|
+
plan = new_plan
|
|
312
|
+
writer({"type": "custom", id: plan_id, "name": "planning", "data": {"plan": plan}})
|
|
313
|
+
additional_kwargs = {"type": "planning", "plan": plan, "update": True}
|
|
314
|
+
ask_user = True
|
|
315
|
+
tool_result = "The user has been shown the plan. Proceed according to their next message."
|
|
316
|
+
|
|
317
|
+
elif tool_name == "save_agent_code":
|
|
318
|
+
self.meta_id = str(uuid.uuid4())
|
|
319
|
+
writer({"type": "custom", id: self.meta_id, "name": "generating", "data": {"update": bool(self.agent)}})
|
|
320
|
+
agent_name, agent_description, python_code = await agent_builder_tools["save_agent_code"].ainvoke(tool_args)
|
|
321
|
+
tool_result, effective_previous_add_context, agent_id = await self.handle_agent_save(
|
|
322
|
+
meta_id=self.meta_id,
|
|
323
|
+
agent_name=agent_name,
|
|
324
|
+
agent_description=agent_description,
|
|
325
|
+
python_code=python_code,
|
|
326
|
+
writer=writer,
|
|
327
|
+
state=state,
|
|
328
|
+
effective_previous_add_context=effective_previous_add_context,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
else:
|
|
333
|
+
raise Exception(
|
|
334
|
+
f"Unexpected tool call: {tool_call['name']}. "
|
|
335
|
+
"tool calls must be one of 'enter_agent_builder_mode', 'execute_ipython_cell', 'load_functions', 'search_functions', 'create_agent_plan', or 'save_agent_code'. For using functions, call them in code using 'execute_ipython_cell'."
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
except Exception as e:
|
|
342
|
+
tool_result = str(e)
|
|
343
|
+
|
|
344
|
+
tool_message = ToolMessage(
|
|
345
|
+
content=json.dumps(tool_result),
|
|
346
|
+
name=tool_call["name"],
|
|
347
|
+
tool_call_id=tool_call["id"],
|
|
348
|
+
)
|
|
349
|
+
tool_messages.append(tool_message)
|
|
350
|
+
|
|
351
|
+
if ask_user:
|
|
352
|
+
tool_messages.append(AIMessage(content=ai_msg, additional_kwargs=additional_kwargs))
|
|
353
|
+
return Command(
|
|
354
|
+
update={
|
|
355
|
+
"messages": tool_messages,
|
|
356
|
+
"selected_tool_ids": new_tool_ids,
|
|
357
|
+
"context": effective_existing_context,
|
|
358
|
+
"add_context": effective_previous_add_context,
|
|
359
|
+
"agent_name": agent_name,
|
|
360
|
+
"agent_description": agent_description,
|
|
361
|
+
"plan": plan,
|
|
362
|
+
}
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
return Command(
|
|
366
|
+
goto="call_model",
|
|
367
|
+
update={
|
|
368
|
+
"messages": tool_messages,
|
|
369
|
+
"selected_tool_ids": new_tool_ids,
|
|
370
|
+
"context": effective_existing_context,
|
|
371
|
+
"add_context": effective_previous_add_context,
|
|
372
|
+
"agent_name": agent_name,
|
|
373
|
+
"agent_description": agent_description,
|
|
374
|
+
"plan": plan,
|
|
375
|
+
},
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
async def route_entry(state: CodeActState) -> Command[Literal["call_model", "execute_tools"]]:
|
|
379
|
+
"""Route to either normal mode or agent builder creation"""
|
|
380
|
+
pre_tools = await self.registry.export_tools(format=ToolFormat.NATIVE)
|
|
381
|
+
|
|
382
|
+
# Create the initial system prompt and tools_context in one go
|
|
383
|
+
self.final_instructions, self.tools_context = create_default_prompt(
|
|
384
|
+
pre_tools,
|
|
385
|
+
self.additional_tools,
|
|
386
|
+
self.instructions,
|
|
387
|
+
await get_connected_apps_string(self.registry),
|
|
388
|
+
self.agent,
|
|
389
|
+
is_initial_prompt=True,
|
|
390
|
+
)
|
|
391
|
+
self.preloaded_defs, _ = build_tool_definitions(pre_tools)
|
|
392
|
+
self.preloaded_defs = "\n".join(self.preloaded_defs)
|
|
393
|
+
await self.registry.load_tools(state["selected_tool_ids"])
|
|
394
|
+
exported_tools = await self.registry.export_tools(
|
|
395
|
+
state["selected_tool_ids"], ToolFormat.NATIVE
|
|
396
|
+
) # Get definition for only the new tools
|
|
397
|
+
_, loaded_tools_context = build_tool_definitions(exported_tools)
|
|
398
|
+
self.tools_context.update(loaded_tools_context)
|
|
399
|
+
|
|
400
|
+
if (
|
|
401
|
+
len(state["messages"]) == 1 and self.agent
|
|
402
|
+
): # Inject the agent's script function into add_context for execution
|
|
403
|
+
script = self.agent.instructions.get("script")
|
|
404
|
+
add_context = {"functions": [script]}
|
|
405
|
+
return Command(goto="call_model", update={"add_context": add_context})
|
|
406
|
+
return Command(goto="call_model")
|
|
407
|
+
|
|
408
|
+
agent = StateGraph(state_schema=CodeActState)
|
|
409
|
+
agent.add_node(call_model, retry_policy=RetryPolicy(max_attempts=3, retry_on=filter_retry_on))
|
|
410
|
+
agent.add_node(execute_tools)
|
|
411
|
+
agent.add_node(route_entry)
|
|
412
|
+
agent.add_edge(START, "route_entry")
|
|
413
|
+
return agent.compile(checkpointer=self.memory)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from typing import Annotated, Literal
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
# Literal type for all available usecase filenames
|
|
6
|
+
UseCaseName = Literal[
|
|
7
|
+
" ",
|
|
8
|
+
"1-unsubscribe",
|
|
9
|
+
"2-reddit",
|
|
10
|
+
"2.1-reddit",
|
|
11
|
+
"3-earnings",
|
|
12
|
+
"4-maps",
|
|
13
|
+
"4.1-maps",
|
|
14
|
+
"5-gmailreply",
|
|
15
|
+
"6-contract",
|
|
16
|
+
"7-overnight",
|
|
17
|
+
"8-sheets_chart",
|
|
18
|
+
"9-learning",
|
|
19
|
+
"10-reddit2",
|
|
20
|
+
"11-github",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ContextSchema(BaseModel):
|
|
25
|
+
"""The configuration for the agent."""
|
|
26
|
+
|
|
27
|
+
base_prompt: str = Field(
|
|
28
|
+
default=" ",
|
|
29
|
+
description="The base prompt to use for the agent's interactions. Leave blank if using a JSON prompt from the dropdown.",
|
|
30
|
+
)
|
|
31
|
+
model_provider: Annotated[
|
|
32
|
+
Literal[
|
|
33
|
+
"openai",
|
|
34
|
+
"anthropic",
|
|
35
|
+
"azure_openai",
|
|
36
|
+
"azure_ai",
|
|
37
|
+
"google_vertexai",
|
|
38
|
+
"google_genai",
|
|
39
|
+
"bedrock",
|
|
40
|
+
"bedrock_converse",
|
|
41
|
+
"cohere",
|
|
42
|
+
"fireworks",
|
|
43
|
+
"together",
|
|
44
|
+
"mistralai",
|
|
45
|
+
"huggingface",
|
|
46
|
+
"groq",
|
|
47
|
+
"ollama",
|
|
48
|
+
"google_anthropic_vertex",
|
|
49
|
+
"deepseek",
|
|
50
|
+
"ibm",
|
|
51
|
+
"nvidia",
|
|
52
|
+
"xai",
|
|
53
|
+
"perplexity",
|
|
54
|
+
],
|
|
55
|
+
{"__template_metadata__": {"kind": "provider"}},
|
|
56
|
+
] = Field(
|
|
57
|
+
default="anthropic",
|
|
58
|
+
description="The name of the model provider to use for the agent's main interactions. ",
|
|
59
|
+
)
|
|
60
|
+
model: Annotated[
|
|
61
|
+
Literal[
|
|
62
|
+
"claude-4-sonnet-20250514",
|
|
63
|
+
"claude-sonnet-4@20250514",
|
|
64
|
+
],
|
|
65
|
+
{"__template_metadata__": {"kind": "llm"}},
|
|
66
|
+
] = Field(
|
|
67
|
+
default="claude-4-sonnet-20250514",
|
|
68
|
+
description="The name of the language model to use for the agent's main interactions. ",
|
|
69
|
+
)
|
|
70
|
+
tool_names: list[str] = Field(
|
|
71
|
+
default=[],
|
|
72
|
+
description="The names of the tools to use for the agent's main interactions. Leave blank if using a JSON prompt from the dropdown.",
|
|
73
|
+
)
|
|
74
|
+
json_prompt_name: UseCaseName = Field(
|
|
75
|
+
default=" ",
|
|
76
|
+
description="The name of the JSON prompt to use for the agent's main interactions, instead of providing a base prompt and tool names. ",
|
|
77
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from universal_mcp.agentr.registry import AgentrRegistry
|
|
2
|
+
|
|
3
|
+
from universal_mcp.agents.codeact01 import CodeActPlaybookAgent
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def agent():
|
|
7
|
+
agent_obj = CodeActPlaybookAgent(
|
|
8
|
+
name="CodeAct Agent",
|
|
9
|
+
instructions="Be very concise in your answers.",
|
|
10
|
+
model="anthropic:claude-4-sonnet-20250514",
|
|
11
|
+
tools=[],
|
|
12
|
+
registry=AgentrRegistry(),
|
|
13
|
+
)
|
|
14
|
+
return await agent_obj._build_graph()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from universal_mcp.agents.codeact01.utils import light_copy
|
|
4
|
+
|
|
5
|
+
MAX_RETRIES = 3
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_context_str(source: Any | list[Any] | dict[str, Any]) -> str:
|
|
9
|
+
"""Converts context to a string representation."""
|
|
10
|
+
if not isinstance(source, dict):
|
|
11
|
+
if isinstance(source, list):
|
|
12
|
+
source = {f"doc_{i + 1}": str(doc) for i, doc in enumerate(source)}
|
|
13
|
+
else:
|
|
14
|
+
source = {"content": str(source)}
|
|
15
|
+
|
|
16
|
+
return "\n".join(f"<{k}>\n{str(v)}\n</{k}>" for k, v in source.items())
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def smart_print(data: Any) -> None:
|
|
20
|
+
"""Prints a dictionary or list of dictionaries with string values truncated to 30 characters.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
data: Either a dictionary with string keys, or a list of such dictionaries
|
|
24
|
+
"""
|
|
25
|
+
print(light_copy(data)) # noqa: T201
|