universal-mcp-agents 0.1.23rc1__py3-none-any.whl → 0.1.23rc3__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.
Potentially problematic release.
This version of universal-mcp-agents might be problematic. Click here for more details.
- universal_mcp/agents/__init__.py +2 -2
- universal_mcp/agents/base.py +2 -2
- universal_mcp/agents/cli.py +1 -1
- universal_mcp/agents/codeact0/__main__.py +2 -5
- universal_mcp/agents/codeact0/agent.py +188 -147
- universal_mcp/agents/codeact0/prompts.py +58 -43
- universal_mcp/agents/codeact0/state.py +13 -13
- universal_mcp/agents/codeact0/tools.py +41 -8
- universal_mcp/agents/codeact0/utils.py +26 -5
- {universal_mcp_agents-0.1.23rc1.dist-info → universal_mcp_agents-0.1.23rc3.dist-info}/METADATA +3 -3
- {universal_mcp_agents-0.1.23rc1.dist-info → universal_mcp_agents-0.1.23rc3.dist-info}/RECORD +12 -12
- {universal_mcp_agents-0.1.23rc1.dist-info → universal_mcp_agents-0.1.23rc3.dist-info}/WHEEL +0 -0
universal_mcp/agents/__init__.py
CHANGED
|
@@ -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/base.py
CHANGED
|
@@ -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/cli.py
CHANGED
|
@@ -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
|
|
|
@@ -1,35 +1,36 @@
|
|
|
1
|
+
import copy
|
|
1
2
|
import json
|
|
2
3
|
import re
|
|
3
|
-
from typing import Literal, cast
|
|
4
4
|
import uuid
|
|
5
|
+
from typing import Literal, cast
|
|
5
6
|
|
|
7
|
+
from langchain_anthropic import ChatAnthropic
|
|
6
8
|
from langchain_core.messages import AIMessage, ToolMessage
|
|
7
9
|
from langchain_core.tools import StructuredTool
|
|
8
10
|
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
9
11
|
from langgraph.graph import START, StateGraph
|
|
10
12
|
from langgraph.types import Command, RetryPolicy, StreamWriter
|
|
11
13
|
from universal_mcp.tools.registry import ToolRegistry
|
|
12
|
-
from universal_mcp.types import
|
|
14
|
+
from universal_mcp.types import ToolFormat
|
|
13
15
|
|
|
14
16
|
from universal_mcp.agents.base import BaseAgent
|
|
15
17
|
from universal_mcp.agents.codeact0.llm_tool import smart_print
|
|
16
18
|
from universal_mcp.agents.codeact0.prompts import (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
AGENT_BUILDER_GENERATING_PROMPT,
|
|
20
|
+
AGENT_BUILDER_META_PROMPT,
|
|
21
|
+
AGENT_BUILDER_PLANNING_PROMPT,
|
|
20
22
|
create_default_prompt,
|
|
21
23
|
)
|
|
22
24
|
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,
|
|
25
|
+
from universal_mcp.agents.codeact0.state import CodeActState, AgentBuilderCode, AgentBuilderMeta, AgentBuilderPlan
|
|
24
26
|
from universal_mcp.agents.codeact0.tools import (
|
|
25
27
|
create_meta_tools,
|
|
26
|
-
|
|
28
|
+
enter_agent_builder_mode,
|
|
27
29
|
get_valid_tools,
|
|
28
30
|
)
|
|
29
|
-
from universal_mcp.agents.codeact0.utils import
|
|
31
|
+
from universal_mcp.agents.codeact0.utils import build_anthropic_cache_message, get_connected_apps_string
|
|
30
32
|
from universal_mcp.agents.llm import load_chat_model
|
|
31
33
|
from universal_mcp.agents.utils import convert_tool_ids_to_dict, filter_retry_on, get_message_text
|
|
32
|
-
from universal_mcp.agents.codeact0.utils import get_connected_apps_string
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
class CodeActPlaybookAgent(BaseAgent):
|
|
@@ -40,7 +41,7 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
40
41
|
model: str,
|
|
41
42
|
memory: BaseCheckpointSaver | None = None,
|
|
42
43
|
registry: ToolRegistry | None = None,
|
|
43
|
-
|
|
44
|
+
agent_builder_registry: object | None = None,
|
|
44
45
|
sandbox_timeout: int = 20,
|
|
45
46
|
**kwargs,
|
|
46
47
|
):
|
|
@@ -52,18 +53,19 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
52
53
|
**kwargs,
|
|
53
54
|
)
|
|
54
55
|
self.model_instance = load_chat_model(model)
|
|
55
|
-
self.
|
|
56
|
+
self.agent_builder_model_instance = load_chat_model("azure/gpt-4.1")
|
|
56
57
|
self.registry = registry
|
|
57
|
-
self.
|
|
58
|
-
self.
|
|
59
|
-
self.tools_config = self.
|
|
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 {}
|
|
60
61
|
self.eval_fn = eval_unsafe
|
|
61
62
|
self.sandbox_timeout = sandbox_timeout
|
|
62
|
-
self.
|
|
63
|
+
self.default_tools_config = {
|
|
63
64
|
"llm": ["generate_text", "classify_data", "extract_data", "call_llm"],
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
self.final_instructions = ""
|
|
67
|
+
self.tools_context = {}
|
|
68
|
+
self.exported_tools = []
|
|
67
69
|
|
|
68
70
|
async def _build_graph(self):
|
|
69
71
|
meta_tools = create_meta_tools(self.registry)
|
|
@@ -71,64 +73,86 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
71
73
|
self.additional_tools = [
|
|
72
74
|
t if isinstance(t, StructuredTool) else StructuredTool.from_function(t) for t in additional_tools
|
|
73
75
|
]
|
|
76
|
+
|
|
74
77
|
if self.tools_config:
|
|
75
|
-
# Convert dict format to list format if needed
|
|
76
78
|
if isinstance(self.tools_config, dict):
|
|
77
79
|
self.tools_config = [
|
|
78
80
|
f"{provider}__{tool}" for provider, tools in self.tools_config.items() for tool in tools
|
|
79
81
|
]
|
|
80
|
-
|
|
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)
|
|
82
87
|
|
|
83
88
|
async def call_model(state: CodeActState) -> Command[Literal["execute_tools"]]:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
|
|
96
117
|
response = cast(AIMessage, model_with_tools.invoke(messages))
|
|
97
118
|
if response.tool_calls:
|
|
98
119
|
return Command(goto="execute_tools", update={"messages": [response]})
|
|
99
120
|
else:
|
|
100
121
|
return Command(update={"messages": [response], "model_with_tools": model_with_tools})
|
|
101
122
|
|
|
102
|
-
async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "
|
|
123
|
+
async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "agent_builder"]]:
|
|
103
124
|
"""Execute tool calls"""
|
|
104
125
|
last_message = state["messages"][-1]
|
|
105
126
|
tool_calls = last_message.tool_calls if isinstance(last_message, AIMessage) else []
|
|
106
127
|
|
|
107
128
|
tool_messages = []
|
|
108
129
|
new_tool_ids = []
|
|
130
|
+
tool_result = ""
|
|
109
131
|
ask_user = False
|
|
110
132
|
ai_msg = ""
|
|
111
|
-
tool_result = ""
|
|
112
133
|
effective_previous_add_context = state.get("add_context", {})
|
|
113
134
|
effective_existing_context = state.get("context", {})
|
|
135
|
+
# logging.info(f"Initial new_tool_ids_for_context: {new_tool_ids_for_context}")
|
|
114
136
|
|
|
115
137
|
for tool_call in tool_calls:
|
|
138
|
+
tool_name = tool_call["name"]
|
|
139
|
+
tool_args = tool_call["args"]
|
|
116
140
|
try:
|
|
117
|
-
if
|
|
141
|
+
if tool_name == "enter_agent_builder_mode":
|
|
118
142
|
tool_message = ToolMessage(
|
|
119
|
-
content=json.dumps("Entered
|
|
143
|
+
content=json.dumps("Entered Agent Builder Mode."),
|
|
120
144
|
name=tool_call["name"],
|
|
121
145
|
tool_call_id=tool_call["id"],
|
|
122
146
|
)
|
|
123
147
|
return Command(
|
|
124
|
-
goto="
|
|
125
|
-
update={"
|
|
148
|
+
goto="agent_builder",
|
|
149
|
+
update={"agent_builder_mode": "planning", "messages": [tool_message]}, # Entered Agent Builder mode
|
|
126
150
|
)
|
|
127
|
-
elif
|
|
151
|
+
elif tool_name == "execute_ipython_cell":
|
|
128
152
|
code = tool_call["args"]["snippet"]
|
|
129
153
|
output, new_context, new_add_context = await handle_execute_ipython_cell(
|
|
130
154
|
code,
|
|
131
|
-
self.tools_context,
|
|
155
|
+
self.tools_context, # Uses the dynamically updated context
|
|
132
156
|
self.eval_fn,
|
|
133
157
|
effective_previous_add_context,
|
|
134
158
|
effective_existing_context,
|
|
@@ -136,23 +160,23 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
136
160
|
effective_existing_context = new_context
|
|
137
161
|
effective_previous_add_context = new_add_context
|
|
138
162
|
tool_result = output
|
|
139
|
-
elif
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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`
|
|
143
167
|
new_tool_ids.extend(valid_tools)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if links:
|
|
168
|
+
if new_tool_ids:
|
|
169
|
+
self.tools_context.update(new_context_for_sandbox)
|
|
170
|
+
if unconnected_links:
|
|
148
171
|
ask_user = True
|
|
149
|
-
ai_msg = f"Please login to the following app(s) using the following links and let me know in order to proceed:\n {
|
|
150
|
-
|
|
151
|
-
|
|
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)
|
|
152
176
|
else:
|
|
153
177
|
raise Exception(
|
|
154
178
|
f"Unexpected tool call: {tool_call['name']}. "
|
|
155
|
-
"tool calls must be one of '
|
|
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'."
|
|
156
180
|
)
|
|
157
181
|
except Exception as e:
|
|
158
182
|
tool_result = str(e)
|
|
@@ -163,13 +187,7 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
163
187
|
tool_call_id=tool_call["id"],
|
|
164
188
|
)
|
|
165
189
|
tool_messages.append(tool_message)
|
|
166
|
-
|
|
167
|
-
if new_tool_ids:
|
|
168
|
-
self.tools_config.extend(new_tool_ids)
|
|
169
|
-
self.exported_tools = await self.registry.export_tools(new_tool_ids, ToolFormat.LANGCHAIN)
|
|
170
|
-
self.final_instructions, self.tools_context = create_default_prompt(
|
|
171
|
-
self.exported_tools, self.additional_tools, self.instructions, await get_connected_apps_string(self.registry), self.playbook
|
|
172
|
-
)
|
|
190
|
+
|
|
173
191
|
if ask_user:
|
|
174
192
|
tool_messages.append(AIMessage(content=ai_msg))
|
|
175
193
|
return Command(
|
|
@@ -191,27 +209,37 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
191
209
|
},
|
|
192
210
|
)
|
|
193
211
|
|
|
194
|
-
def
|
|
195
|
-
|
|
196
|
-
if
|
|
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":
|
|
197
215
|
plan_id = str(uuid.uuid4())
|
|
198
|
-
writer({
|
|
199
|
-
|
|
200
|
-
id: plan_id,
|
|
201
|
-
"name": "planning",
|
|
202
|
-
"data": {"update": bool(self.playbook)}
|
|
203
|
-
})
|
|
204
|
-
planning_instructions = self.instructions + PLAYBOOK_PLANNING_PROMPT
|
|
216
|
+
writer({"type": "custom", id: plan_id, "name": "planning", "data": {"update": bool(self.agent)}})
|
|
217
|
+
planning_instructions = self.instructions + AGENT_BUILDER_PLANNING_PROMPT
|
|
205
218
|
messages = [{"role": "system", "content": planning_instructions}] + state["messages"]
|
|
206
219
|
|
|
207
|
-
model_with_structured_output = self.
|
|
220
|
+
model_with_structured_output = self.agent_builder_model_instance.with_structured_output(AgentBuilderPlan)
|
|
208
221
|
response = model_with_structured_output.invoke(messages)
|
|
209
|
-
plan = cast(
|
|
210
|
-
|
|
222
|
+
plan = cast(AgentBuilderPlan, response)
|
|
223
|
+
|
|
211
224
|
writer({"type": "custom", id: plan_id, "name": "planning", "data": {"plan": plan.steps}})
|
|
212
|
-
return Command(
|
|
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
|
+
)
|
|
213
241
|
|
|
214
|
-
elif
|
|
242
|
+
elif agent_builder_mode == "confirming":
|
|
215
243
|
# Deterministic routing based on three exact button inputs from UI
|
|
216
244
|
user_text = ""
|
|
217
245
|
for m in reversed(state["messages"]):
|
|
@@ -227,139 +255,152 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
227
255
|
if t == "yes, this is great":
|
|
228
256
|
self.meta_id = str(uuid.uuid4())
|
|
229
257
|
name, description = None, None
|
|
230
|
-
if self.
|
|
258
|
+
if self.agent:
|
|
231
259
|
# Update flow: use existing name/description and do not re-generate
|
|
232
|
-
name = getattr(self.
|
|
233
|
-
description = getattr(self.
|
|
234
|
-
writer(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
"
|
|
240
|
-
|
|
241
|
-
|
|
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
|
+
},
|
|
242
272
|
}
|
|
243
|
-
|
|
273
|
+
)
|
|
244
274
|
else:
|
|
245
|
-
writer({
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
"name": "generating",
|
|
249
|
-
"data": {"update": False}
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
meta_instructions = self.instructions + PLAYBOOK_META_PROMPT
|
|
275
|
+
writer({"type": "custom", id: self.meta_id, "name": "generating", "data": {"update": False}})
|
|
276
|
+
|
|
277
|
+
meta_instructions = self.instructions + AGENT_BUILDER_META_PROMPT
|
|
253
278
|
messages = [{"role": "system", "content": meta_instructions}] + state["messages"]
|
|
254
279
|
|
|
255
|
-
model_with_structured_output = self.
|
|
280
|
+
model_with_structured_output = self.agent_builder_model_instance.with_structured_output(AgentBuilderMeta)
|
|
256
281
|
meta_response = model_with_structured_output.invoke(messages)
|
|
257
|
-
meta = cast(
|
|
282
|
+
meta = cast(AgentBuilderMeta, meta_response)
|
|
258
283
|
name, description = meta.name, meta.description
|
|
259
284
|
|
|
260
285
|
# Emit intermediary UI update with created name/description
|
|
261
|
-
writer(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
+
)
|
|
269
303
|
if t == "i would like to modify the plan":
|
|
270
|
-
prompt_ai = AIMessage(
|
|
271
|
-
|
|
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]})
|
|
272
309
|
if t == "let's do something else":
|
|
273
|
-
return Command(goto="call_model", update={"
|
|
310
|
+
return Command(goto="call_model", update={"agent_builder_mode": "inactive"})
|
|
274
311
|
|
|
275
312
|
# Fallback safe default
|
|
276
|
-
return Command(goto="call_model", update={"
|
|
313
|
+
return Command(goto="call_model", update={"agent_builder_mode": "inactive"})
|
|
277
314
|
|
|
278
|
-
elif
|
|
279
|
-
generating_instructions = self.instructions +
|
|
315
|
+
elif agent_builder_mode == "generating":
|
|
316
|
+
generating_instructions = self.instructions + AGENT_BUILDER_GENERATING_PROMPT
|
|
280
317
|
messages = [{"role": "system", "content": generating_instructions}] + state["messages"]
|
|
281
|
-
|
|
282
|
-
model_with_structured_output = self.
|
|
318
|
+
|
|
319
|
+
model_with_structured_output = self.agent_builder_model_instance.with_structured_output(AgentBuilderCode)
|
|
283
320
|
response = model_with_structured_output.invoke(messages)
|
|
284
|
-
func_code = cast(
|
|
321
|
+
func_code = cast(AgentBuilderCode, response).code
|
|
285
322
|
|
|
286
323
|
# Extract function name (handle both regular and async functions)
|
|
287
324
|
match = re.search(r"^\s*(?:async\s+)?def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(", func_code, re.MULTILINE)
|
|
288
325
|
if match:
|
|
289
326
|
function_name = match.group(1)
|
|
290
327
|
else:
|
|
291
|
-
function_name = "
|
|
328
|
+
function_name = "generated_agent"
|
|
292
329
|
|
|
293
330
|
# Use generated metadata if available
|
|
294
|
-
final_name = state.get("
|
|
295
|
-
final_description = state.get("
|
|
331
|
+
final_name = state.get("agent_name") or function_name
|
|
332
|
+
final_description = state.get("agent_description") or f"Generated agent: {function_name}"
|
|
296
333
|
|
|
297
334
|
# Save or update an Agent using the helper registry
|
|
298
335
|
try:
|
|
299
|
-
if not self.
|
|
300
|
-
raise ValueError("
|
|
336
|
+
if not self.agent_builder_registry:
|
|
337
|
+
raise ValueError("AgentBuilder registry is not configured")
|
|
301
338
|
|
|
302
339
|
# Build instructions payload embedding the plan and function code
|
|
303
340
|
instructions_payload = {
|
|
304
|
-
"
|
|
305
|
-
"
|
|
341
|
+
"plan": state["plan"],
|
|
342
|
+
"script": func_code,
|
|
306
343
|
}
|
|
307
344
|
|
|
308
345
|
# Convert tool ids list to dict
|
|
309
346
|
tool_dict = convert_tool_ids_to_dict(state["selected_tool_ids"])
|
|
310
347
|
|
|
311
|
-
res = self.
|
|
348
|
+
res = self.agent_builder_registry.upsert_agent(
|
|
312
349
|
name=final_name,
|
|
313
350
|
description=final_description,
|
|
314
351
|
instructions=instructions_payload,
|
|
315
352
|
tools=tool_dict,
|
|
316
|
-
visibility="private",
|
|
317
353
|
)
|
|
318
354
|
except Exception as e:
|
|
319
355
|
raise e
|
|
320
356
|
|
|
321
|
-
writer(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
"
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
+
},
|
|
330
368
|
}
|
|
331
|
-
|
|
369
|
+
)
|
|
332
370
|
mock_assistant_message = AIMessage(
|
|
333
|
-
content=json.dumps(response.
|
|
371
|
+
content=json.dumps(response.model_dump()),
|
|
334
372
|
additional_kwargs={
|
|
335
373
|
"type": "generating",
|
|
336
374
|
"id": str(res.id),
|
|
337
|
-
"update": bool(self.
|
|
375
|
+
"update": bool(self.agent),
|
|
338
376
|
"name": final_name,
|
|
339
377
|
"description": final_description,
|
|
340
378
|
},
|
|
341
379
|
)
|
|
342
380
|
|
|
343
|
-
return Command(
|
|
344
|
-
|
|
345
|
-
|
|
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)
|
|
346
387
|
|
|
347
|
-
|
|
348
|
-
"""Route to either normal mode or playbook creation"""
|
|
349
|
-
self.exported_tools = []
|
|
350
|
-
self.tools_config.extend(state.get("selected_tool_ids", []))
|
|
351
|
-
self.exported_tools = await self.registry.export_tools(self.tools_config, ToolFormat.LANGCHAIN)
|
|
388
|
+
# Create the initial system prompt and tools_context in one go
|
|
352
389
|
self.final_instructions, self.tools_context = create_default_prompt(
|
|
353
|
-
|
|
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,
|
|
354
396
|
)
|
|
355
|
-
if state.get("
|
|
356
|
-
return "
|
|
397
|
+
if state.get("agent_builder_mode") in ["planning", "confirming", "generating"]:
|
|
398
|
+
return "agent_builder"
|
|
357
399
|
return "call_model"
|
|
358
400
|
|
|
359
401
|
agent = StateGraph(state_schema=CodeActState)
|
|
360
402
|
agent.add_node(call_model, retry_policy=RetryPolicy(max_attempts=3, retry_on=filter_retry_on))
|
|
361
|
-
agent.add_node(
|
|
403
|
+
agent.add_node(agent_builder)
|
|
362
404
|
agent.add_node(execute_tools)
|
|
363
405
|
agent.add_conditional_edges(START, route_entry)
|
|
364
|
-
# agent.add_edge(START, "call_model")
|
|
365
406
|
return agent.compile(checkpointer=self.memory)
|
|
@@ -3,6 +3,7 @@ import re
|
|
|
3
3
|
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
|
|
7
8
|
|
|
8
9
|
uneditable_prompt = """
|
|
@@ -62,16 +63,16 @@ Rules:
|
|
|
62
63
|
- Your final response should contain the complete answer to the user's request in a clear, well-formatted manner that directly addresses what they asked for.
|
|
63
64
|
"""
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
AGENT_BUILDER_PLANNING_PROMPT = """Now, you are tasked with creating a reusable agent from the user's previous workflow.
|
|
66
67
|
|
|
67
68
|
TASK: Analyze the conversation history and code execution to create a step-by-step plan for a reusable function.
|
|
68
69
|
Do not include the searching and loading of tools. Assume that the tools have already been loaded.
|
|
69
70
|
The plan is a sequence of steps.
|
|
70
|
-
You must output a JSON object with a single key "steps", which is a list of strings. Each string is a step in the
|
|
71
|
+
You must output a JSON object with a single key "steps", which is a list of strings. Each string is a step in the agent.
|
|
71
72
|
|
|
72
73
|
Your plan should:
|
|
73
74
|
1. Identify the key steps in the workflow
|
|
74
|
-
2. Mark user-specific variables that should become the main
|
|
75
|
+
2. Mark user-specific variables that should become the main agent function parameters using `variable_name` syntax. Intermediate variables should not be highlighted using ``
|
|
75
76
|
3. Keep the logic generic and reusable
|
|
76
77
|
4. Be clear and concise
|
|
77
78
|
|
|
@@ -89,26 +90,26 @@ Now create a plan based on the conversation history. Do not include any other te
|
|
|
89
90
|
"""
|
|
90
91
|
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
AGENT_BUILDER_GENERATING_PROMPT = """Now, you are tasked with generating the agent function.
|
|
93
94
|
Your response must be ONLY the Python code for the function.
|
|
94
95
|
Do not include any other text, markdown, or explanations in your response.
|
|
95
96
|
Your response should start with `def` or `async def`.
|
|
96
97
|
The function should be a single, complete piece of code that can be executed independently, based on previously executed code snippets that executed correctly.
|
|
97
|
-
The parameters of the function should be the same as the final confirmed
|
|
98
|
+
The parameters of the function should be the same as the final confirmed agent plan.
|
|
98
99
|
"""
|
|
99
100
|
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
You are preparing metadata for a reusable
|
|
102
|
+
AGENT_BUILDER_META_PROMPT = """
|
|
103
|
+
You are preparing metadata for a reusable agent based on the confirmed step-by-step plan.
|
|
103
104
|
|
|
104
|
-
TASK: Create a concise, human-friendly name and a short description for the
|
|
105
|
+
TASK: Create a concise, human-friendly name and a short description for the agent.
|
|
105
106
|
|
|
106
107
|
INPUTS:
|
|
107
108
|
- Conversation context and plan steps will be provided in prior messages
|
|
108
109
|
|
|
109
110
|
REQUIREMENTS:
|
|
110
111
|
1. Name: 3-6 words, Title Case, no punctuation except hyphens if needed
|
|
111
|
-
2. Description: Single sentence, <= 140 characters, clearly states what the
|
|
112
|
+
2. Description: Single sentence, <= 140 characters, clearly states what the agent does
|
|
112
113
|
|
|
113
114
|
OUTPUT: Return ONLY a JSON object with exactly these keys:
|
|
114
115
|
{
|
|
@@ -136,14 +137,21 @@ def create_default_prompt(
|
|
|
136
137
|
additional_tools: Sequence[StructuredTool],
|
|
137
138
|
base_prompt: str | None = None,
|
|
138
139
|
apps_string: str | None = None,
|
|
139
|
-
|
|
140
|
+
agent: object | None = None,
|
|
141
|
+
is_initial_prompt: bool = False,
|
|
140
142
|
):
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
if is_initial_prompt:
|
|
144
|
+
system_prompt = uneditable_prompt.strip()
|
|
145
|
+
if apps_string:
|
|
146
|
+
system_prompt += f"\n\n**Connected external applications (These apps have been logged into by the user):**\n{apps_string}\n\n Use `search_functions` to search for functions you can perform using the above. You can also discover more applications using the `search_functions` tool to find additional tools and integrations, if required.\n"
|
|
147
|
+
system_prompt += (
|
|
148
|
+
"\n\nIn addition to the Python Standard Library, you can use the following external functions:\n"
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
system_prompt = ""
|
|
152
|
+
|
|
146
153
|
tools_context = {}
|
|
154
|
+
tool_definitions = []
|
|
147
155
|
|
|
148
156
|
for tool in tools:
|
|
149
157
|
if hasattr(tool, "func") and tool.func is not None:
|
|
@@ -152,10 +160,11 @@ def create_default_prompt(
|
|
|
152
160
|
elif hasattr(tool, "coroutine") and tool.coroutine is not None:
|
|
153
161
|
tool_callable = tool.coroutine
|
|
154
162
|
is_async = True
|
|
155
|
-
|
|
163
|
+
tool_definitions.append(
|
|
164
|
+
f'''{"async " if is_async else ""}{schema_to_signature(tool.args, tool.name)}:
|
|
156
165
|
"""{tool.description}"""
|
|
157
|
-
...
|
|
158
|
-
|
|
166
|
+
...'''
|
|
167
|
+
)
|
|
159
168
|
safe_name = make_safe_function_name(tool.name)
|
|
160
169
|
tools_context[safe_name] = tool_callable
|
|
161
170
|
|
|
@@ -166,34 +175,40 @@ def create_default_prompt(
|
|
|
166
175
|
elif hasattr(tool, "coroutine") and tool.coroutine is not None:
|
|
167
176
|
tool_callable = tool.coroutine
|
|
168
177
|
is_async = True
|
|
169
|
-
|
|
178
|
+
tool_definitions.append(
|
|
179
|
+
f'''{"async " if is_async else ""}def {tool.name} {str(inspect.signature(tool_callable))}:
|
|
170
180
|
"""{tool.description}"""
|
|
171
|
-
...
|
|
172
|
-
|
|
181
|
+
...'''
|
|
182
|
+
)
|
|
173
183
|
safe_name = make_safe_function_name(tool.name)
|
|
174
184
|
tools_context[safe_name] = tool_callable
|
|
175
185
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
186
|
+
system_prompt += "\n".join(tool_definitions)
|
|
187
|
+
|
|
188
|
+
if is_initial_prompt:
|
|
189
|
+
if base_prompt and base_prompt.strip():
|
|
190
|
+
system_prompt += (
|
|
191
|
+
f"\n\nUse the following information/instructions while completing your tasks:\n\n{base_prompt}"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Append existing agent (plan + code) if provided
|
|
195
|
+
try:
|
|
196
|
+
if agent and hasattr(agent, "instructions"):
|
|
197
|
+
pb = agent.instructions or {}
|
|
198
|
+
plan = pb.get("plan")
|
|
199
|
+
code = pb.get("script")
|
|
200
|
+
if plan or code:
|
|
201
|
+
system_prompt += "\n\nExisting Agent Provided:\n"
|
|
202
|
+
if plan:
|
|
203
|
+
if isinstance(plan, list):
|
|
204
|
+
plan_block = "\n".join(f"- {str(s)}" for s in plan)
|
|
205
|
+
else:
|
|
206
|
+
plan_block = str(plan)
|
|
207
|
+
system_prompt += f"Plan Steps:\n{plan_block}\n"
|
|
208
|
+
if code:
|
|
209
|
+
system_prompt += f"\nScript:\n```python\n{str(code)}\n```\n"
|
|
210
|
+
except Exception:
|
|
211
|
+
# Silently ignore formatting issues
|
|
212
|
+
pass
|
|
198
213
|
|
|
199
214
|
return system_prompt, tools_context
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
from typing import Annotated, Any
|
|
1
|
+
from typing import Annotated, Any
|
|
2
2
|
|
|
3
3
|
from langgraph.prebuilt.chat_agent_executor import AgentState
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class
|
|
8
|
-
steps:
|
|
7
|
+
class AgentBuilderPlan(BaseModel):
|
|
8
|
+
steps: list[str] = Field(description="The steps of the agent.")
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
12
|
-
code: str = Field(description="The Python code for the
|
|
11
|
+
class AgentBuilderCode(BaseModel):
|
|
12
|
+
code: str = Field(description="The Python code for the agent.")
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class
|
|
16
|
-
name: str = Field(description="Concise, title-cased
|
|
15
|
+
class AgentBuilderMeta(BaseModel):
|
|
16
|
+
name: str = Field(description="Concise, title-cased agent name (3-6 words).")
|
|
17
17
|
description: str = Field(description="Short, one-sentence description (<= 140 chars).")
|
|
18
18
|
|
|
19
19
|
|
|
@@ -46,13 +46,13 @@ class CodeActState(AgentState):
|
|
|
46
46
|
"""Dictionary containing the execution context with available tools and variables."""
|
|
47
47
|
add_context: dict[str, Any]
|
|
48
48
|
"""Dictionary containing the additional context (functions, classes, imports) to be added to the execution context."""
|
|
49
|
-
|
|
50
|
-
"""State for the
|
|
49
|
+
agent_builder_mode: str | None
|
|
50
|
+
"""State for the agent builder agent."""
|
|
51
51
|
selected_tool_ids: Annotated[list[str], _enqueue]
|
|
52
52
|
"""Queue for tools exported from registry"""
|
|
53
53
|
plan: list[str] | None
|
|
54
|
-
"""Plan for the
|
|
55
|
-
|
|
56
|
-
"""Generated
|
|
57
|
-
|
|
54
|
+
"""Plan for the agent builder agent."""
|
|
55
|
+
agent_name: str | None
|
|
56
|
+
"""Generated agent name after confirmation."""
|
|
57
|
+
agent_description: str | None
|
|
58
58
|
"""Generated short description after confirmation."""
|
|
@@ -4,16 +4,18 @@ from collections import defaultdict
|
|
|
4
4
|
from typing import Annotated, Any
|
|
5
5
|
|
|
6
6
|
from langchain_core.tools import tool
|
|
7
|
-
from loguru import logger
|
|
8
7
|
from pydantic import Field
|
|
9
8
|
from universal_mcp.agentr.registry import AgentrRegistry
|
|
10
9
|
from universal_mcp.types import ToolFormat
|
|
11
10
|
|
|
11
|
+
from universal_mcp.agents.codeact0.prompts import create_default_prompt
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
|
|
14
|
+
def enter_agent_builder_mode():
|
|
15
|
+
"""Call this function to enter agent builder mode. Agent builder mode is when the user wants to store a repeated task as a script with some inputs for the future."""
|
|
15
16
|
return
|
|
16
17
|
|
|
18
|
+
|
|
17
19
|
def create_meta_tools(tool_registry: AgentrRegistry) -> dict[str, Any]:
|
|
18
20
|
"""Create the meta tools for searching and loading tools"""
|
|
19
21
|
|
|
@@ -99,7 +101,9 @@ def create_meta_tools(tool_registry: AgentrRegistry) -> dict[str, Any]:
|
|
|
99
101
|
prioritized_app_id_list = [canonical_app_id]
|
|
100
102
|
else:
|
|
101
103
|
# 1. Perform an initial broad search for tools.
|
|
102
|
-
initial_tool_search_tasks = [
|
|
104
|
+
initial_tool_search_tasks = [
|
|
105
|
+
registry.search_tools(query=q, distance_threshold=THRESHOLD) for q in queries
|
|
106
|
+
]
|
|
103
107
|
initial_tool_results = await asyncio.gather(*initial_tool_search_tasks)
|
|
104
108
|
|
|
105
109
|
# 2. Search for relevant apps.
|
|
@@ -192,15 +196,44 @@ def create_meta_tools(tool_registry: AgentrRegistry) -> dict[str, Any]:
|
|
|
192
196
|
|
|
193
197
|
@tool
|
|
194
198
|
async def load_functions(tool_ids: list[str]) -> str:
|
|
195
|
-
"""
|
|
199
|
+
"""
|
|
200
|
+
Loads specified functions and returns their Python signatures and docstrings.
|
|
201
|
+
This makes the functions available for use inside the 'execute_ipython_cell' tool.
|
|
202
|
+
The agent MUST use the returned information to understand how to call the functions correctly.
|
|
196
203
|
|
|
197
204
|
Args:
|
|
198
|
-
tool_ids:
|
|
205
|
+
tool_ids: A list of function IDs in the format 'app__function'. Example: ['google_mail__send_email']
|
|
199
206
|
|
|
200
207
|
Returns:
|
|
201
|
-
|
|
208
|
+
A string containing the signatures and docstrings of the successfully loaded functions,
|
|
209
|
+
ready for the agent to use in its code.
|
|
202
210
|
"""
|
|
203
|
-
|
|
211
|
+
if not tool_ids:
|
|
212
|
+
return "No tool IDs provided to load."
|
|
213
|
+
|
|
214
|
+
# Step 1: Validate which tools are usable and get login links for others.
|
|
215
|
+
valid_tools, unconnected_links = await get_valid_tools(tool_ids=tool_ids, registry=tool_registry)
|
|
216
|
+
|
|
217
|
+
if not valid_tools:
|
|
218
|
+
return "Error: None of the provided tool IDs could be validated or loaded."
|
|
219
|
+
|
|
220
|
+
# Step 2: Export the schemas of the valid tools.
|
|
221
|
+
all_exported_tools = await tool_registry.export_tools(valid_tools, ToolFormat.LANGCHAIN)
|
|
222
|
+
exported_tools = [tool for tool in all_exported_tools if tool.name in valid_tools]
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# Step 3: Build the informational string for the agent.
|
|
226
|
+
tool_definitions, new_tools_context = create_default_prompt(exported_tools, [], is_initial_prompt=False)
|
|
227
|
+
|
|
228
|
+
result_parts = [
|
|
229
|
+
f"Successfully loaded {len(exported_tools)} functions. They are now available for use inside `execute_ipython_cell`:",
|
|
230
|
+
tool_definitions,
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
response_string = "\n\n".join(result_parts)
|
|
234
|
+
unconnected_links = "\n".join(unconnected_links)
|
|
235
|
+
|
|
236
|
+
return response_string, new_tools_context, valid_tools, unconnected_links
|
|
204
237
|
|
|
205
238
|
@tool
|
|
206
239
|
async def web_search(query: str) -> dict:
|
|
@@ -10,6 +10,26 @@ from universal_mcp.types import ToolConfig
|
|
|
10
10
|
MAX_CHARS = 5000
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def build_anthropic_cache_message(text: str, role: str = "system", ttl: str = "1h") -> list[dict[str, Any]]:
|
|
14
|
+
"""Build a complete Anthropic cache messages array from text.
|
|
15
|
+
|
|
16
|
+
Returns a list with a single cache message whose content is the
|
|
17
|
+
cached Anthropic content array with ephemeral cache control and TTL.
|
|
18
|
+
"""
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
"role": role,
|
|
22
|
+
"content": [
|
|
23
|
+
{
|
|
24
|
+
"type": "text",
|
|
25
|
+
"text": text,
|
|
26
|
+
"cache_control": {"type": "ephemeral", "ttl": ttl},
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
13
33
|
def add_tools(tool_config: ToolConfig, tools_to_add: ToolConfig):
|
|
14
34
|
for app_id, new_tools in tools_to_add.items():
|
|
15
35
|
all_tools = tool_config.get(app_id, []) + new_tools
|
|
@@ -375,6 +395,7 @@ def schema_to_signature(schema: dict, func_name: str = "my_function") -> str:
|
|
|
375
395
|
param_str = ",\n ".join(params)
|
|
376
396
|
return f"def {func_name}(\n {param_str},\n):"
|
|
377
397
|
|
|
398
|
+
|
|
378
399
|
def smart_truncate(
|
|
379
400
|
output: str, max_chars_full: int = 2000, max_lines_headtail: int = 20, summary_threshold: int = 10000
|
|
380
401
|
) -> str:
|
|
@@ -413,21 +434,21 @@ async def get_connected_apps_string(registry) -> str:
|
|
|
413
434
|
"""Get a formatted string of connected applications from the registry."""
|
|
414
435
|
if not registry:
|
|
415
436
|
return ""
|
|
416
|
-
|
|
437
|
+
|
|
417
438
|
try:
|
|
418
439
|
# Get connected apps from registry
|
|
419
440
|
connections = await registry.list_connected_apps()
|
|
420
441
|
if not connections:
|
|
421
442
|
return "No applications are currently connected."
|
|
422
|
-
|
|
443
|
+
|
|
423
444
|
# Extract app names from connections
|
|
424
445
|
connected_app_ids = {connection["app_id"] for connection in connections}
|
|
425
|
-
|
|
446
|
+
|
|
426
447
|
# Format the apps list
|
|
427
448
|
apps_list = []
|
|
428
449
|
for app_id in connected_app_ids:
|
|
429
450
|
apps_list.append(f"- {app_id}")
|
|
430
|
-
|
|
451
|
+
|
|
431
452
|
return "\n".join(apps_list)
|
|
432
453
|
except Exception:
|
|
433
|
-
return "Unable to retrieve connected applications."
|
|
454
|
+
return "Unable to retrieve connected applications."
|
{universal_mcp_agents-0.1.23rc1.dist-info → universal_mcp_agents-0.1.23rc3.dist-info}/METADATA
RENAMED
|
@@ -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'
|
{universal_mcp_agents-0.1.23rc1.dist-info → universal_mcp_agents-0.1.23rc3.dist-info}/RECORD
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
universal_mcp/agents/__init__.py,sha256=
|
|
2
|
-
universal_mcp/agents/base.py,sha256=
|
|
3
|
-
universal_mcp/agents/cli.py,sha256=
|
|
1
|
+
universal_mcp/agents/__init__.py,sha256=bW7WJopR6YZSLxghLf8nhohhHPWzm0wdGoZlmKDAcZ4,1078
|
|
2
|
+
universal_mcp/agents/base.py,sha256=sSC217rac89dEheeIRZoMSU_nk9UfSfQw-0E1BjNSIc,7393
|
|
3
|
+
universal_mcp/agents/cli.py,sha256=9CG7majpWUz7C6t0d8xr-Sg2ZPKBuQdykTbYS6KIZ3A,922
|
|
4
4
|
universal_mcp/agents/hil.py,sha256=_5PCK6q0goGm8qylJq44aSp2MadP-yCPvhOJYKqWLMo,3808
|
|
5
5
|
universal_mcp/agents/llm.py,sha256=hVRwjZs3MHl5_3BWedmurs2Jt1oZDfFX0Zj9F8KH7fk,1787
|
|
6
6
|
universal_mcp/agents/react.py,sha256=8XQvJ0HLVgc-K0qn9Ml48WGcgUGuIKtL67HatlT6Da0,3334
|
|
@@ -21,16 +21,16 @@ universal_mcp/agents/builder/helper.py,sha256=8igR1b3Gy_N2u3WxHYKIWzvw7F5BMnfpO2
|
|
|
21
21
|
universal_mcp/agents/builder/prompts.py,sha256=8Xs6uzTUHguDRngVMLak3lkXFkk2VV_uQXaDllzP5cI,4670
|
|
22
22
|
universal_mcp/agents/builder/state.py,sha256=7DeWllxfN-yD6cd9wJ3KIgjO8TctkJvVjAbZT8W_zqk,922
|
|
23
23
|
universal_mcp/agents/codeact0/__init__.py,sha256=8-fvUo1Sm6dURGI-lW-X3Kd78LqySYbb5NMkNJ4NDwg,76
|
|
24
|
-
universal_mcp/agents/codeact0/__main__.py,sha256=
|
|
25
|
-
universal_mcp/agents/codeact0/agent.py,sha256
|
|
24
|
+
universal_mcp/agents/codeact0/__main__.py,sha256=EHW9ePVePEemGI5yMUBc2Mp_JlrP6Apk1liab1y2Rd8,782
|
|
25
|
+
universal_mcp/agents/codeact0/agent.py,sha256=LzCshVduxT8weYbZ2mOyL1-rp4o-8H1ODoZAaONAV6o,19322
|
|
26
26
|
universal_mcp/agents/codeact0/config.py,sha256=H-1woj_nhSDwf15F63WYn723y4qlRefXzGxuH81uYF0,2215
|
|
27
27
|
universal_mcp/agents/codeact0/langgraph_agent.py,sha256=8nz2wq-LexImx-l1y9_f81fK72IQetnCeljwgnduNGY,420
|
|
28
28
|
universal_mcp/agents/codeact0/llm_tool.py,sha256=-pAz04OrbZ_dJ2ueysT1qZd02DrbLY4EbU0tiuF_UNU,798
|
|
29
|
-
universal_mcp/agents/codeact0/prompts.py,sha256=
|
|
29
|
+
universal_mcp/agents/codeact0/prompts.py,sha256=PEBPHvw4yItNVMAaaVUfNVhp53iijxs283Ln6xhew-4,11623
|
|
30
30
|
universal_mcp/agents/codeact0/sandbox.py,sha256=Xw4tbUV_6haYIZZvteJi6lIYsW6ni_3DCRCOkslTKgM,4459
|
|
31
|
-
universal_mcp/agents/codeact0/state.py,sha256=
|
|
32
|
-
universal_mcp/agents/codeact0/tools.py,sha256=
|
|
33
|
-
universal_mcp/agents/codeact0/utils.py,sha256=
|
|
31
|
+
universal_mcp/agents/codeact0/state.py,sha256=241G-ZDIs4dqm_RnfLZuZOBr7llJOh4dQoDc0aiRcPo,1947
|
|
32
|
+
universal_mcp/agents/codeact0/tools.py,sha256=ZvAqi2vwarTlPizVdu9cZIAoujtBTF4OWnd69rdIIyA,14711
|
|
33
|
+
universal_mcp/agents/codeact0/utils.py,sha256=Gvft0W0Sg1qlFWm8ciX14yssCa8y3x037lql92yGsBQ,18164
|
|
34
34
|
universal_mcp/agents/shared/__main__.py,sha256=XxH5qGDpgFWfq7fwQfgKULXGiUgeTp_YKfcxftuVZq8,1452
|
|
35
35
|
universal_mcp/agents/shared/prompts.py,sha256=yjP3zbbuKi87qCj21qwTTicz8TqtkKgnyGSeEjMu3ho,3761
|
|
36
36
|
universal_mcp/agents/shared/tool_node.py,sha256=DC9F-Ri28Pam0u3sXWNODVgmj9PtAEUb5qP1qOoGgfs,9169
|
|
@@ -39,6 +39,6 @@ universal_mcp/applications/filesystem/app.py,sha256=0TRjjm8YnslVRSmfkXI7qQOAlqWl
|
|
|
39
39
|
universal_mcp/applications/llm/__init__.py,sha256=_XGRxN3O1--ZS5joAsPf8IlI9Qa6negsJrwJ5VJXno0,46
|
|
40
40
|
universal_mcp/applications/llm/app.py,sha256=g9mK-luOLUshZzBGyQZMOHBeCSXmh2kCKir40YnsGUo,12727
|
|
41
41
|
universal_mcp/applications/ui/app.py,sha256=c7OkZsO2fRtndgAzAQbKu-1xXRuRp9Kjgml57YD2NR4,9459
|
|
42
|
-
universal_mcp_agents-0.1.
|
|
43
|
-
universal_mcp_agents-0.1.
|
|
44
|
-
universal_mcp_agents-0.1.
|
|
42
|
+
universal_mcp_agents-0.1.23rc3.dist-info/METADATA,sha256=-Bv-20WOl1vY3WTMTmZe0eUShg6RG7vcmPt9HuUFXnU,881
|
|
43
|
+
universal_mcp_agents-0.1.23rc3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
44
|
+
universal_mcp_agents-0.1.23rc3.dist-info/RECORD,,
|
|
File without changes
|