universal-mcp-agents 0.1.21__py3-none-any.whl → 0.1.23__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 -8
- universal_mcp/agents/base.py +44 -34
- universal_mcp/agents/bigtool/state.py +1 -1
- universal_mcp/agents/cli.py +2 -2
- universal_mcp/agents/codeact0/__main__.py +2 -5
- universal_mcp/agents/codeact0/agent.py +318 -136
- universal_mcp/agents/codeact0/prompts.py +227 -107
- universal_mcp/agents/codeact0/sandbox.py +21 -17
- universal_mcp/agents/codeact0/state.py +18 -9
- universal_mcp/agents/codeact0/tools.py +394 -174
- universal_mcp/agents/codeact0/utils.py +119 -11
- universal_mcp/agents/llm.py +10 -4
- universal_mcp/agents/react.py +3 -3
- universal_mcp/agents/sandbox.py +124 -69
- universal_mcp/applications/llm/app.py +20 -19
- {universal_mcp_agents-0.1.21.dist-info → universal_mcp_agents-0.1.23.dist-info}/METADATA +6 -5
- {universal_mcp_agents-0.1.21.dist-info → universal_mcp_agents-0.1.23.dist-info}/RECORD +18 -18
- {universal_mcp_agents-0.1.21.dist-info → universal_mcp_agents-0.1.23.dist-info}/WHEEL +0 -0
|
@@ -1,31 +1,33 @@
|
|
|
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
|
|
|
6
|
-
from
|
|
7
|
-
from langchain_core.
|
|
7
|
+
from langchain_anthropic import ChatAnthropic
|
|
8
|
+
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
|
|
8
9
|
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
9
10
|
from langgraph.graph import START, StateGraph
|
|
10
11
|
from langgraph.types import Command, RetryPolicy, StreamWriter
|
|
11
12
|
from universal_mcp.tools.registry import ToolRegistry
|
|
12
|
-
from universal_mcp.types import
|
|
13
|
+
from universal_mcp.types import ToolFormat
|
|
13
14
|
|
|
14
15
|
from universal_mcp.agents.base import BaseAgent
|
|
15
16
|
from universal_mcp.agents.codeact0.llm_tool import smart_print
|
|
16
17
|
from universal_mcp.agents.codeact0.prompts import (
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
AGENT_BUILDER_GENERATING_PROMPT,
|
|
19
|
+
AGENT_BUILDER_META_PROMPT,
|
|
20
|
+
AGENT_BUILDER_PLANNING_PROMPT,
|
|
21
|
+
build_tool_definitions,
|
|
19
22
|
create_default_prompt,
|
|
20
23
|
)
|
|
21
24
|
from universal_mcp.agents.codeact0.sandbox import eval_unsafe, execute_ipython_cell, handle_execute_ipython_cell
|
|
22
|
-
from universal_mcp.agents.codeact0.state import
|
|
25
|
+
from universal_mcp.agents.codeact0.state import AgentBuilderCode, AgentBuilderMeta, AgentBuilderPlan, CodeActState
|
|
23
26
|
from universal_mcp.agents.codeact0.tools import (
|
|
24
27
|
create_meta_tools,
|
|
25
|
-
|
|
26
|
-
get_valid_tools,
|
|
28
|
+
enter_agent_builder_mode,
|
|
27
29
|
)
|
|
28
|
-
from universal_mcp.agents.codeact0.utils import
|
|
30
|
+
from universal_mcp.agents.codeact0.utils import build_anthropic_cache_message, get_connected_apps_string, strip_thinking
|
|
29
31
|
from universal_mcp.agents.llm import load_chat_model
|
|
30
32
|
from universal_mcp.agents.utils import convert_tool_ids_to_dict, filter_retry_on, get_message_text
|
|
31
33
|
|
|
@@ -37,9 +39,8 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
37
39
|
instructions: str,
|
|
38
40
|
model: str,
|
|
39
41
|
memory: BaseCheckpointSaver | None = None,
|
|
40
|
-
tools: ToolConfig | None = None,
|
|
41
42
|
registry: ToolRegistry | None = None,
|
|
42
|
-
|
|
43
|
+
agent_builder_registry: object | None = None,
|
|
43
44
|
sandbox_timeout: int = 20,
|
|
44
45
|
**kwargs,
|
|
45
46
|
):
|
|
@@ -51,83 +52,107 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
51
52
|
**kwargs,
|
|
52
53
|
)
|
|
53
54
|
self.model_instance = load_chat_model(model)
|
|
54
|
-
self.
|
|
55
|
-
self.tools_config = tools or {}
|
|
55
|
+
self.agent_builder_model_instance = load_chat_model("anthropic:claude-sonnet-4-5-20250929", thinking=False)
|
|
56
56
|
self.registry = registry
|
|
57
|
-
self.
|
|
58
|
-
self.
|
|
57
|
+
self.agent_builder_registry = agent_builder_registry
|
|
58
|
+
self.agent = agent_builder_registry.get_agent() if agent_builder_registry else None
|
|
59
|
+
|
|
60
|
+
self.tools_config = self.agent.tools if self.agent else {}
|
|
59
61
|
self.eval_fn = eval_unsafe
|
|
60
62
|
self.sandbox_timeout = sandbox_timeout
|
|
61
|
-
self.
|
|
63
|
+
self.default_tools_config = {
|
|
62
64
|
"llm": ["generate_text", "classify_data", "extract_data", "call_llm"],
|
|
63
|
-
"markitdown": ["convert_to_markdown"],
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
+
self.final_instructions = ""
|
|
67
|
+
self.tools_context = {}
|
|
68
|
+
self.eval_mode = kwargs.get("eval_mode", False)
|
|
66
69
|
|
|
67
|
-
async def _build_graph(self):
|
|
70
|
+
async def _build_graph(self): # noqa: PLR0915
|
|
71
|
+
"""Build the graph for the CodeAct Playbook Agent."""
|
|
68
72
|
meta_tools = create_meta_tools(self.registry)
|
|
69
|
-
additional_tools = [smart_print, meta_tools["web_search"]]
|
|
70
73
|
self.additional_tools = [
|
|
71
|
-
|
|
74
|
+
smart_print,
|
|
75
|
+
meta_tools["web_search"],
|
|
76
|
+
meta_tools["read_file"],
|
|
77
|
+
meta_tools["save_file"],
|
|
78
|
+
meta_tools["upload_file"],
|
|
72
79
|
]
|
|
80
|
+
|
|
73
81
|
if self.tools_config:
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
f"{provider}__{tool}" for provider, tools in self.tools_config.items() for tool in tools
|
|
78
|
-
]
|
|
79
|
-
if not self.registry:
|
|
80
|
-
raise ValueError("Tools are configured but no registry is provided")
|
|
82
|
+
await self.registry.load_tools(self.tools_config) # Load provided tools
|
|
83
|
+
if self.default_tools_config:
|
|
84
|
+
await self.registry.load_tools(self.default_tools_config) # Load default tools
|
|
81
85
|
|
|
82
86
|
async def call_model(state: CodeActState) -> Command[Literal["execute_tools"]]:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
"""This node now only ever binds the four meta-tools to the LLM."""
|
|
88
|
+
messages = build_anthropic_cache_message(self.final_instructions) + state["messages"]
|
|
89
|
+
|
|
90
|
+
agent_facing_tools = [
|
|
91
|
+
execute_ipython_cell,
|
|
92
|
+
enter_agent_builder_mode,
|
|
93
|
+
meta_tools["search_functions"],
|
|
94
|
+
meta_tools["load_functions"],
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
if isinstance(self.model_instance, ChatAnthropic):
|
|
98
|
+
model_with_tools = self.model_instance.bind_tools(
|
|
99
|
+
tools=agent_facing_tools,
|
|
100
|
+
tool_choice="auto",
|
|
101
|
+
cache_control={"type": "ephemeral", "ttl": "1h"},
|
|
102
|
+
)
|
|
103
|
+
if isinstance(messages[-1].content, str):
|
|
104
|
+
pass
|
|
105
|
+
else:
|
|
106
|
+
last = copy.deepcopy(messages[-1])
|
|
107
|
+
last.content[-1]["cache_control"] = {"type": "ephemeral", "ttl": "5m"}
|
|
108
|
+
messages[-1] = last
|
|
109
|
+
else:
|
|
110
|
+
model_with_tools = self.model_instance.bind_tools(
|
|
111
|
+
tools=agent_facing_tools,
|
|
112
|
+
tool_choice="auto",
|
|
113
|
+
)
|
|
114
|
+
response = cast(AIMessage, await model_with_tools.ainvoke(messages))
|
|
96
115
|
if response.tool_calls:
|
|
97
116
|
return Command(goto="execute_tools", update={"messages": [response]})
|
|
98
117
|
else:
|
|
99
118
|
return Command(update={"messages": [response], "model_with_tools": model_with_tools})
|
|
100
119
|
|
|
101
|
-
async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "
|
|
120
|
+
async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "agent_builder"]]:
|
|
102
121
|
"""Execute tool calls"""
|
|
103
122
|
last_message = state["messages"][-1]
|
|
104
123
|
tool_calls = last_message.tool_calls if isinstance(last_message, AIMessage) else []
|
|
105
124
|
|
|
106
125
|
tool_messages = []
|
|
107
126
|
new_tool_ids = []
|
|
127
|
+
tool_result = ""
|
|
108
128
|
ask_user = False
|
|
109
129
|
ai_msg = ""
|
|
110
|
-
tool_result = ""
|
|
111
130
|
effective_previous_add_context = state.get("add_context", {})
|
|
112
131
|
effective_existing_context = state.get("context", {})
|
|
132
|
+
# logging.info(f"Initial new_tool_ids_for_context: {new_tool_ids_for_context}")
|
|
113
133
|
|
|
114
134
|
for tool_call in tool_calls:
|
|
135
|
+
tool_name = tool_call["name"]
|
|
136
|
+
tool_args = tool_call["args"]
|
|
115
137
|
try:
|
|
116
|
-
if
|
|
138
|
+
if tool_name == "enter_agent_builder_mode":
|
|
117
139
|
tool_message = ToolMessage(
|
|
118
|
-
content=json.dumps("Entered
|
|
140
|
+
content=json.dumps("Entered Agent Builder Mode."),
|
|
119
141
|
name=tool_call["name"],
|
|
120
142
|
tool_call_id=tool_call["id"],
|
|
121
143
|
)
|
|
122
144
|
return Command(
|
|
123
|
-
goto="
|
|
124
|
-
update={
|
|
145
|
+
goto="agent_builder",
|
|
146
|
+
update={
|
|
147
|
+
"agent_builder_mode": "planning",
|
|
148
|
+
"messages": [tool_message],
|
|
149
|
+
}, # Entered Agent Builder mode
|
|
125
150
|
)
|
|
126
|
-
elif
|
|
151
|
+
elif tool_name == "execute_ipython_cell":
|
|
127
152
|
code = tool_call["args"]["snippet"]
|
|
128
153
|
output, new_context, new_add_context = await handle_execute_ipython_cell(
|
|
129
154
|
code,
|
|
130
|
-
self.tools_context,
|
|
155
|
+
self.tools_context, # Uses the dynamically updated context
|
|
131
156
|
self.eval_fn,
|
|
132
157
|
effective_previous_add_context,
|
|
133
158
|
effective_existing_context,
|
|
@@ -135,23 +160,25 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
135
160
|
effective_existing_context = new_context
|
|
136
161
|
effective_previous_add_context = new_add_context
|
|
137
162
|
tool_result = output
|
|
138
|
-
elif
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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[
|
|
166
|
+
"load_functions"
|
|
167
|
+
].ainvoke(tool_args)
|
|
168
|
+
# We still need to update the sandbox context for `execute_ipython_cell`
|
|
142
169
|
new_tool_ids.extend(valid_tools)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if links:
|
|
170
|
+
if new_tool_ids:
|
|
171
|
+
self.tools_context.update(new_context_for_sandbox)
|
|
172
|
+
if unconnected_links:
|
|
147
173
|
ask_user = True
|
|
148
|
-
ai_msg = f"Please login to the following app(s) using the following links and let me know in order to proceed:\n {
|
|
149
|
-
|
|
150
|
-
|
|
174
|
+
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} "
|
|
175
|
+
|
|
176
|
+
elif tool_name == "search_functions":
|
|
177
|
+
tool_result = await meta_tools["search_functions"].ainvoke(tool_args)
|
|
151
178
|
else:
|
|
152
179
|
raise Exception(
|
|
153
180
|
f"Unexpected tool call: {tool_call['name']}. "
|
|
154
|
-
"tool calls must be one of '
|
|
181
|
+
"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'."
|
|
155
182
|
)
|
|
156
183
|
except Exception as e:
|
|
157
184
|
tool_result = str(e)
|
|
@@ -163,12 +190,6 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
163
190
|
)
|
|
164
191
|
tool_messages.append(tool_message)
|
|
165
192
|
|
|
166
|
-
if new_tool_ids:
|
|
167
|
-
self.tools_config.extend(new_tool_ids)
|
|
168
|
-
self.exported_tools = await self.registry.export_tools(new_tool_ids, ToolFormat.LANGCHAIN)
|
|
169
|
-
self.final_instructions, self.tools_context = create_default_prompt(
|
|
170
|
-
self.exported_tools, self.additional_tools, self.instructions, playbook=self.playbook
|
|
171
|
-
)
|
|
172
193
|
if ask_user:
|
|
173
194
|
tool_messages.append(AIMessage(content=ai_msg))
|
|
174
195
|
return Command(
|
|
@@ -190,27 +211,50 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
190
211
|
},
|
|
191
212
|
)
|
|
192
213
|
|
|
193
|
-
def
|
|
194
|
-
|
|
195
|
-
if
|
|
214
|
+
async def agent_builder(state: CodeActState, writer: StreamWriter) -> Command[Literal["call_model"]]:
|
|
215
|
+
agent_builder_mode = state.get("agent_builder_mode")
|
|
216
|
+
if agent_builder_mode == "planning":
|
|
196
217
|
plan_id = str(uuid.uuid4())
|
|
197
|
-
writer({
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
response = model_with_structured_output.invoke(messages)
|
|
208
|
-
plan = cast(PlaybookPlan, response)
|
|
209
|
-
|
|
218
|
+
writer({"type": "custom", id: plan_id, "name": "planning", "data": {"update": bool(self.agent)}})
|
|
219
|
+
planning_instructions = self.instructions + AGENT_BUILDER_PLANNING_PROMPT + self.preloaded_defs
|
|
220
|
+
messages = [{"role": "system", "content": planning_instructions}] + strip_thinking(state["messages"])
|
|
221
|
+
|
|
222
|
+
model_with_structured_output = self.agent_builder_model_instance.with_structured_output(
|
|
223
|
+
AgentBuilderPlan
|
|
224
|
+
)
|
|
225
|
+
response = await model_with_structured_output.ainvoke(messages)
|
|
226
|
+
plan = cast(AgentBuilderPlan, response)
|
|
227
|
+
|
|
210
228
|
writer({"type": "custom", id: plan_id, "name": "planning", "data": {"plan": plan.steps}})
|
|
211
|
-
|
|
229
|
+
ai_msg = AIMessage(
|
|
230
|
+
content=json.dumps(plan.model_dump()),
|
|
231
|
+
additional_kwargs={
|
|
232
|
+
"type": "planning",
|
|
233
|
+
"plan": plan.steps,
|
|
234
|
+
"update": bool(self.agent),
|
|
235
|
+
},
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
if self.eval_mode:
|
|
239
|
+
mock_user_message = HumanMessage(content="yes, this is great")
|
|
240
|
+
return Command(
|
|
241
|
+
goto="agent_builder",
|
|
242
|
+
update={
|
|
243
|
+
"messages": [ai_msg, mock_user_message],
|
|
244
|
+
"agent_builder_mode": "generating",
|
|
245
|
+
"plan": plan.steps,
|
|
246
|
+
},
|
|
247
|
+
)
|
|
212
248
|
|
|
213
|
-
|
|
249
|
+
return Command(
|
|
250
|
+
update={
|
|
251
|
+
"messages": [ai_msg],
|
|
252
|
+
"agent_builder_mode": "confirming",
|
|
253
|
+
"plan": plan.steps,
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
elif agent_builder_mode == "confirming":
|
|
214
258
|
# Deterministic routing based on three exact button inputs from UI
|
|
215
259
|
user_text = ""
|
|
216
260
|
for m in reversed(state["messages"]):
|
|
@@ -224,90 +268,228 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
224
268
|
|
|
225
269
|
t = user_text.lower()
|
|
226
270
|
if t == "yes, this is great":
|
|
227
|
-
|
|
271
|
+
self.meta_id = str(uuid.uuid4())
|
|
272
|
+
name, description = None, None
|
|
273
|
+
if self.agent:
|
|
274
|
+
# Update flow: use existing name/description and do not re-generate
|
|
275
|
+
name = getattr(self.agent, "name", None)
|
|
276
|
+
description = getattr(self.agent, "description", None)
|
|
277
|
+
writer(
|
|
278
|
+
{
|
|
279
|
+
"type": "custom",
|
|
280
|
+
id: self.meta_id,
|
|
281
|
+
"name": "generating",
|
|
282
|
+
"data": {
|
|
283
|
+
"update": True,
|
|
284
|
+
"name": name,
|
|
285
|
+
"description": description,
|
|
286
|
+
},
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
else:
|
|
290
|
+
writer({"type": "custom", id: self.meta_id, "name": "generating", "data": {"update": False}})
|
|
291
|
+
|
|
292
|
+
meta_instructions = self.instructions + AGENT_BUILDER_META_PROMPT
|
|
293
|
+
messages = [{"role": "system", "content": meta_instructions}] + state["messages"]
|
|
294
|
+
|
|
295
|
+
model_with_structured_output = self.agent_builder_model_instance.with_structured_output(
|
|
296
|
+
AgentBuilderMeta
|
|
297
|
+
)
|
|
298
|
+
meta_response = await model_with_structured_output.ainvoke(messages)
|
|
299
|
+
meta = cast(AgentBuilderMeta, meta_response)
|
|
300
|
+
name, description = meta.name, meta.description
|
|
301
|
+
|
|
302
|
+
# Emit intermediary UI update with created name/description
|
|
303
|
+
writer(
|
|
304
|
+
{
|
|
305
|
+
"type": "custom",
|
|
306
|
+
id: self.meta_id,
|
|
307
|
+
"name": "generating",
|
|
308
|
+
"data": {"update": False, "name": name, "description": description},
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
return Command(
|
|
313
|
+
goto="agent_builder",
|
|
314
|
+
update={
|
|
315
|
+
"agent_builder_mode": "generating",
|
|
316
|
+
"agent_name": name,
|
|
317
|
+
"agent_description": description,
|
|
318
|
+
},
|
|
319
|
+
)
|
|
228
320
|
if t == "i would like to modify the plan":
|
|
229
|
-
prompt_ai = AIMessage(
|
|
230
|
-
|
|
321
|
+
prompt_ai = AIMessage(
|
|
322
|
+
content="What would you like to change about the plan? Let me know and I'll update the plan accordingly.",
|
|
323
|
+
additional_kwargs={"stream": "true"},
|
|
324
|
+
)
|
|
325
|
+
return Command(update={"agent_builder_mode": "planning", "messages": [prompt_ai]})
|
|
231
326
|
if t == "let's do something else":
|
|
232
|
-
return Command(goto="call_model", update={"
|
|
327
|
+
return Command(goto="call_model", update={"agent_builder_mode": "inactive"})
|
|
233
328
|
|
|
234
329
|
# Fallback safe default
|
|
235
|
-
return Command(goto="call_model", update={"
|
|
236
|
-
|
|
237
|
-
elif
|
|
238
|
-
|
|
239
|
-
writer({
|
|
240
|
-
"type": "custom",
|
|
241
|
-
id: generate_id,
|
|
242
|
-
"name": "generating",
|
|
243
|
-
"data": {"update": bool(self.playbook)}
|
|
244
|
-
})
|
|
245
|
-
generating_instructions = self.instructions + PLAYBOOK_GENERATING_PROMPT
|
|
330
|
+
return Command(goto="call_model", update={"agent_builder_mode": "inactive"})
|
|
331
|
+
|
|
332
|
+
elif agent_builder_mode == "generating":
|
|
333
|
+
generating_instructions = self.instructions + AGENT_BUILDER_GENERATING_PROMPT + self.preloaded_defs
|
|
246
334
|
messages = [{"role": "system", "content": generating_instructions}] + state["messages"]
|
|
247
|
-
|
|
248
|
-
model_with_structured_output = self.
|
|
249
|
-
|
|
250
|
-
|
|
335
|
+
|
|
336
|
+
model_with_structured_output = self.agent_builder_model_instance.with_structured_output(
|
|
337
|
+
AgentBuilderCode
|
|
338
|
+
)
|
|
339
|
+
response = await model_with_structured_output.ainvoke(messages)
|
|
340
|
+
func_code = cast(AgentBuilderCode, response).code
|
|
251
341
|
|
|
252
342
|
# Extract function name (handle both regular and async functions)
|
|
253
343
|
match = re.search(r"^\s*(?:async\s+)?def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(", func_code, re.MULTILINE)
|
|
254
344
|
if match:
|
|
255
345
|
function_name = match.group(1)
|
|
256
346
|
else:
|
|
257
|
-
function_name = "
|
|
347
|
+
function_name = "generated_agent"
|
|
348
|
+
|
|
349
|
+
# Use generated metadata if available
|
|
350
|
+
final_name = state.get("agent_name") or function_name
|
|
351
|
+
final_description = state.get("agent_description") or f"Generated agent: {function_name}"
|
|
258
352
|
|
|
259
353
|
# Save or update an Agent using the helper registry
|
|
260
354
|
try:
|
|
261
|
-
if not self.
|
|
262
|
-
raise ValueError("
|
|
355
|
+
if not self.agent_builder_registry:
|
|
356
|
+
raise ValueError("AgentBuilder registry is not configured")
|
|
263
357
|
|
|
264
358
|
# Build instructions payload embedding the plan and function code
|
|
265
359
|
instructions_payload = {
|
|
266
|
-
"
|
|
267
|
-
"
|
|
360
|
+
"plan": state["plan"],
|
|
361
|
+
"script": func_code,
|
|
268
362
|
}
|
|
269
363
|
|
|
270
364
|
# Convert tool ids list to dict
|
|
271
365
|
tool_dict = convert_tool_ids_to_dict(state["selected_tool_ids"])
|
|
272
366
|
|
|
273
|
-
res = self.
|
|
274
|
-
name=
|
|
275
|
-
description=
|
|
367
|
+
res = self.agent_builder_registry.upsert_agent(
|
|
368
|
+
name=final_name,
|
|
369
|
+
description=final_description,
|
|
276
370
|
instructions=instructions_payload,
|
|
277
371
|
tools=tool_dict,
|
|
278
|
-
visibility="private",
|
|
279
372
|
)
|
|
280
|
-
except Exception
|
|
281
|
-
|
|
373
|
+
except Exception:
|
|
374
|
+
# In case of error, add the code to the exit message content
|
|
375
|
+
|
|
376
|
+
mock_exit_tool_call = {"name": "exit_agent_builder_mode", "args": {}, "id": "exit_builder_1"}
|
|
377
|
+
|
|
378
|
+
# Create a minimal assistant message to maintain flow
|
|
379
|
+
mock_assistant_message = AIMessage(
|
|
380
|
+
content=json.dumps(response.model_dump()),
|
|
381
|
+
tool_calls=[mock_exit_tool_call],
|
|
382
|
+
additional_kwargs={
|
|
383
|
+
"type": "generating",
|
|
384
|
+
"id": "ignore",
|
|
385
|
+
"update": bool(self.agent),
|
|
386
|
+
"name": final_name.replace(" ", "_"),
|
|
387
|
+
"description": final_description,
|
|
388
|
+
},
|
|
389
|
+
)
|
|
390
|
+
mock_exit_tool_response = ToolMessage(
|
|
391
|
+
content=json.dumps(
|
|
392
|
+
f"An error occurred. Displaying the function code:\n\n{func_code}\nFinal Name: {final_name}\nDescription: {final_description}"
|
|
393
|
+
),
|
|
394
|
+
name="exit_agent_builder_mode",
|
|
395
|
+
tool_call_id="exit_builder_1",
|
|
396
|
+
)
|
|
397
|
+
if self.eval_mode:
|
|
398
|
+
human_msg = HumanMessage(
|
|
399
|
+
content="Run the generated agent code and check whether it works as expected"
|
|
400
|
+
)
|
|
401
|
+
return Command(
|
|
402
|
+
goto="call_model",
|
|
403
|
+
update={
|
|
404
|
+
"messages": [mock_assistant_message, mock_exit_tool_response, human_msg],
|
|
405
|
+
"agent_builder_mode": "normal",
|
|
406
|
+
},
|
|
407
|
+
)
|
|
408
|
+
return Command(
|
|
409
|
+
update={
|
|
410
|
+
"messages": [mock_assistant_message, mock_exit_tool_response],
|
|
411
|
+
"agent_builder_mode": "normal",
|
|
412
|
+
}
|
|
413
|
+
)
|
|
282
414
|
|
|
283
|
-
writer(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
415
|
+
writer(
|
|
416
|
+
{
|
|
417
|
+
"type": "custom",
|
|
418
|
+
id: self.meta_id,
|
|
419
|
+
"name": "generating",
|
|
420
|
+
"data": {
|
|
421
|
+
"id": str(res.id),
|
|
422
|
+
"update": bool(self.agent),
|
|
423
|
+
"name": final_name,
|
|
424
|
+
"description": final_description,
|
|
425
|
+
},
|
|
426
|
+
}
|
|
427
|
+
)
|
|
428
|
+
mock_exit_tool_call = {"name": "exit_agent_builder_mode", "args": {}, "id": "exit_builder_1"}
|
|
429
|
+
mock_assistant_message = AIMessage(
|
|
430
|
+
content=json.dumps(response.model_dump()),
|
|
431
|
+
tool_calls=[mock_exit_tool_call],
|
|
432
|
+
additional_kwargs={
|
|
433
|
+
"type": "generating",
|
|
434
|
+
"id": str(res.id),
|
|
435
|
+
"update": bool(self.agent),
|
|
436
|
+
"name": final_name.replace(" ", "_"),
|
|
437
|
+
"description": final_description,
|
|
438
|
+
},
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
mock_exit_tool_response = ToolMessage(
|
|
442
|
+
content=json.dumps(
|
|
443
|
+
"Exited Agent Builder Mode. Enter this mode again if you need to modify the saved agent."
|
|
444
|
+
),
|
|
445
|
+
name="exit_agent_builder_mode",
|
|
446
|
+
tool_call_id="exit_builder_1",
|
|
447
|
+
)
|
|
290
448
|
|
|
291
449
|
return Command(
|
|
292
|
-
update={
|
|
450
|
+
update={
|
|
451
|
+
"messages": [mock_assistant_message, mock_exit_tool_response],
|
|
452
|
+
"agent_builder_mode": "normal",
|
|
453
|
+
}
|
|
293
454
|
)
|
|
294
455
|
|
|
295
|
-
async def route_entry(state: CodeActState) -> Literal["call_model", "
|
|
296
|
-
"""Route to either normal mode or
|
|
297
|
-
self.
|
|
298
|
-
|
|
299
|
-
|
|
456
|
+
async def route_entry(state: CodeActState) -> Command[Literal["call_model", "agent_builder", "execute_tools"]]:
|
|
457
|
+
"""Route to either normal mode or agent builder creation"""
|
|
458
|
+
pre_tools = await self.registry.export_tools(format=ToolFormat.NATIVE)
|
|
459
|
+
|
|
460
|
+
# Create the initial system prompt and tools_context in one go
|
|
300
461
|
self.final_instructions, self.tools_context = create_default_prompt(
|
|
301
|
-
|
|
462
|
+
pre_tools,
|
|
463
|
+
self.additional_tools,
|
|
464
|
+
self.instructions,
|
|
465
|
+
await get_connected_apps_string(self.registry),
|
|
466
|
+
self.agent,
|
|
467
|
+
is_initial_prompt=True,
|
|
302
468
|
)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
469
|
+
self.preloaded_defs, _ = build_tool_definitions(pre_tools)
|
|
470
|
+
self.preloaded_defs = "\n".join(self.preloaded_defs)
|
|
471
|
+
await self.registry.load_tools(state["selected_tool_ids"])
|
|
472
|
+
exported_tools = await self.registry.export_tools(
|
|
473
|
+
state["selected_tool_ids"], ToolFormat.NATIVE
|
|
474
|
+
) # Get definition for only the new tools
|
|
475
|
+
_, loaded_tools_context = build_tool_definitions(exported_tools)
|
|
476
|
+
self.tools_context.update(loaded_tools_context)
|
|
477
|
+
|
|
478
|
+
if (
|
|
479
|
+
len(state["messages"]) == 1 and self.agent
|
|
480
|
+
): # Inject the agent's script function into add_context for execution
|
|
481
|
+
script = self.agent.instructions.get("script")
|
|
482
|
+
add_context = {"functions": [script]}
|
|
483
|
+
return Command(goto="call_model", update={"add_context": add_context})
|
|
484
|
+
|
|
485
|
+
if state.get("agent_builder_mode") in ["planning", "confirming", "generating"]:
|
|
486
|
+
return Command(goto="agent_builder")
|
|
487
|
+
return Command(goto="call_model")
|
|
306
488
|
|
|
307
489
|
agent = StateGraph(state_schema=CodeActState)
|
|
308
490
|
agent.add_node(call_model, retry_policy=RetryPolicy(max_attempts=3, retry_on=filter_retry_on))
|
|
309
|
-
agent.add_node(
|
|
491
|
+
agent.add_node(agent_builder)
|
|
310
492
|
agent.add_node(execute_tools)
|
|
311
|
-
agent.
|
|
312
|
-
|
|
493
|
+
agent.add_node(route_entry)
|
|
494
|
+
agent.add_edge(START, "route_entry")
|
|
313
495
|
return agent.compile(checkpointer=self.memory)
|