universal-mcp-agents 0.1.12__py3-none-any.whl → 0.1.14__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 +1 -1
- universal_mcp/agents/base.py +2 -0
- universal_mcp/agents/bigtool/__init__.py +1 -1
- universal_mcp/agents/bigtool/agent.py +2 -2
- universal_mcp/agents/bigtool/graph.py +65 -31
- universal_mcp/agents/bigtool/prompts.py +2 -2
- universal_mcp/agents/bigtool/tools.py +18 -4
- universal_mcp/agents/builder/__main__.py +105 -30
- universal_mcp/agents/builder/builder.py +149 -160
- universal_mcp/agents/builder/helper.py +73 -0
- universal_mcp/agents/builder/prompts.py +33 -152
- universal_mcp/agents/builder/state.py +1 -1
- universal_mcp/agents/cli.py +2 -2
- universal_mcp/agents/codeact/agent.py +1 -1
- universal_mcp/agents/codeact/sandbox.py +1 -5
- universal_mcp/agents/codeact0/agent.py +5 -4
- universal_mcp/agents/codeact0/langgraph_agent.py +17 -0
- universal_mcp/agents/codeact0/llm_tool.py +1 -1
- universal_mcp/agents/codeact0/prompts.py +34 -23
- universal_mcp/agents/codeact0/usecases/11-github.yaml +6 -5
- universal_mcp/agents/codeact0/utils.py +42 -63
- universal_mcp/agents/shared/__main__.py +43 -0
- universal_mcp/agents/shared/prompts.py +50 -99
- universal_mcp/agents/shared/tool_node.py +149 -203
- universal_mcp/agents/utils.py +65 -0
- universal_mcp/applications/ui/app.py +2 -2
- {universal_mcp_agents-0.1.12.dist-info → universal_mcp_agents-0.1.14.dist-info}/METADATA +1 -1
- {universal_mcp_agents-0.1.12.dist-info → universal_mcp_agents-0.1.14.dist-info}/RECORD +29 -28
- universal_mcp/agents/codeact0/langgraph_graph.py +0 -17
- universal_mcp/agents/codeact0/legacy_codeact.py +0 -104
- {universal_mcp_agents-0.1.12.dist-info → universal_mcp_agents-0.1.14.dist-info}/WHEEL +0 -0
universal_mcp/agents/cli.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
1
3
|
from langgraph.checkpoint.memory import MemorySaver
|
|
2
4
|
from typer import Typer
|
|
3
5
|
from universal_mcp.agentr.client import AgentrClient
|
|
@@ -6,8 +8,6 @@ from universal_mcp.logger import setup_logger
|
|
|
6
8
|
|
|
7
9
|
from universal_mcp.agents import get_agent
|
|
8
10
|
|
|
9
|
-
import asyncio
|
|
10
|
-
|
|
11
11
|
app = Typer()
|
|
12
12
|
|
|
13
13
|
|
|
@@ -48,7 +48,7 @@ class CodeActAgent(BaseAgent):
|
|
|
48
48
|
memory=memory,
|
|
49
49
|
**kwargs,
|
|
50
50
|
)
|
|
51
|
-
self.model_instance = load_chat_model(model
|
|
51
|
+
self.model_instance = load_chat_model(model)
|
|
52
52
|
self.tools_config = tools or {}
|
|
53
53
|
self.registry = registry
|
|
54
54
|
self.eval_fn = eval_unsafe
|
|
@@ -51,17 +51,13 @@ SAFE_BUILTINS = {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
# (The SAFE_BUILTINS definition remains the same)
|
|
55
|
-
# ...
|
|
56
|
-
|
|
57
|
-
|
|
58
54
|
async def eval_unsafe(
|
|
59
55
|
code: str, _locals: dict[str, Callable], timeout: int = 10
|
|
60
56
|
) -> tuple[SandboxOutput, dict[str, Any]]:
|
|
61
57
|
"""Executes a string of Python code in a sandboxed environment."""
|
|
62
58
|
original_keys = set(_locals.keys())
|
|
63
59
|
execution_context = _locals.copy()
|
|
64
|
-
execution_context["__builtins__"] = SAFE_BUILTINS
|
|
60
|
+
execution_context["__builtins__"] = __builtins__ # TODO: Use SAFE_BUILTINS instead of __builtins__
|
|
65
61
|
|
|
66
62
|
stdout_capture = io.StringIO()
|
|
67
63
|
output = SandboxOutput(stdout="")
|
|
@@ -18,7 +18,8 @@ from universal_mcp.agents.codeact0.prompts import (
|
|
|
18
18
|
)
|
|
19
19
|
from universal_mcp.agents.codeact0.sandbox import eval_unsafe, execute_ipython_cell
|
|
20
20
|
from universal_mcp.agents.codeact0.state import CodeActState
|
|
21
|
-
from universal_mcp.agents.
|
|
21
|
+
from universal_mcp.agents.utils import filter_retry_on
|
|
22
|
+
from universal_mcp.agents.codeact0.utils import inject_context
|
|
22
23
|
from universal_mcp.agents.llm import load_chat_model
|
|
23
24
|
|
|
24
25
|
|
|
@@ -63,9 +64,9 @@ class CodeActAgent(BaseAgent):
|
|
|
63
64
|
raise ValueError("Tools are configured but no registry is provided")
|
|
64
65
|
# Langchain tools are fine
|
|
65
66
|
exported_tools = await self.registry.export_tools(self.tools_config, ToolFormat.LANGCHAIN)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
self.instructions, self.tools_context = create_default_prompt(
|
|
67
|
+
additional_tools= [smart_print, data_extractor, ai_classify, call_llm]
|
|
68
|
+
additional_tools = [t if isinstance(t, StructuredTool) else create_tool(t) for t in additional_tools]
|
|
69
|
+
self.instructions, self.tools_context = create_default_prompt(exported_tools, additional_tools, self.instructions)
|
|
69
70
|
|
|
70
71
|
def call_model(state: CodeActState) -> Command[Literal["sandbox"]]:
|
|
71
72
|
messages = [{"role": "system", "content": self.instructions}] + state["messages"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
4
|
+
from rich import print
|
|
5
|
+
from universal_mcp.agentr.registry import AgentrRegistry
|
|
6
|
+
|
|
7
|
+
from universal_mcp.agents.codeact0.agent import CodeActAgent
|
|
8
|
+
from universal_mcp.agents.utils import messages_to_list
|
|
9
|
+
async def agent():
|
|
10
|
+
agent_obj = CodeActAgent(
|
|
11
|
+
name="CodeAct Agent",
|
|
12
|
+
instructions="Be very concise in your answers.",
|
|
13
|
+
model="anthropic:claude-4-sonnet-20250514",
|
|
14
|
+
tools={"google_calendar": ["get_upcoming_events"], "exa" : ["search_with_filters"]},
|
|
15
|
+
registry=AgentrRegistry()
|
|
16
|
+
)
|
|
17
|
+
return await agent_obj._build_graph()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
uneditable_prompt = """
|
|
2
|
-
You are
|
|
2
|
+
You are Wingmen, an AI Assistant created by AgentR. You are a creative, straight-forward and direct principal software engineer.
|
|
3
3
|
|
|
4
4
|
Your job is to answer the user's question or perform the task they ask for.
|
|
5
5
|
- Answer simple questions (which do not require you to write any code or access any external resources) directly. Note that any operation that involves using ONLY print functions should be answered directly.
|
|
@@ -7,19 +7,18 @@ Your job is to answer the user's question or perform the task they ask for.
|
|
|
7
7
|
- You have access to `execute_ipython_cell` tool that allows you to execute Python code in an IPython notebook cell.
|
|
8
8
|
- In writing or natural language processing tasks DO NOT answer directly. Instead use `execute_ipython_cell` tool with the AI functions provided to you for tasks like summarizing, text generation, classification, data extraction from text or unstructured data, etc.
|
|
9
9
|
- The code you write will be executed in a sandbox environment, and you can use the output of previous executions in your code.
|
|
10
|
-
- Read and understand the output of the previous code snippet and use it to answer the user's request. Note that the code output is
|
|
10
|
+
- Read and understand the output of the previous code snippet and use it to answer the user's request. Note that the code output is NOT visible to the user, so after the task is complete, you have to give the output to the user in a markdown format.
|
|
11
11
|
- If needed, feel free to ask for more information from the user (without using the execute_ipython_cell tool) to clarify the task.
|
|
12
12
|
|
|
13
13
|
GUIDELINES for writing code:
|
|
14
14
|
- Variables defined at the top level of previous code snippets can be referenced in your code.
|
|
15
|
-
- External functions which return a dict or list[dict] are ambiguous. Therefore, you MUST explore the structure of the returned data using `smart_print()` statements before using it, printing keys and values.
|
|
16
|
-
- Ensure to not print large amounts of data, use string truncation to limit the output to a few lines when checking the data structure.
|
|
15
|
+
- External functions which return a dict or list[dict] are ambiguous. Therefore, you MUST explore the structure of the returned data using `smart_print()` statements before using it, printing keys and values. `smart_print` truncates long strings from data, preventing huge output logs.
|
|
17
16
|
- When an operation involves running a fixed set of steps on a list of items, run one run correctly and then use a for loop to run the steps on each item in the list.
|
|
18
17
|
- In a single code snippet, try to achieve as much as possible.
|
|
19
|
-
- You can only import libraries that come pre-installed with Python
|
|
20
|
-
-
|
|
18
|
+
- You can only import libraries that come pre-installed with Python.
|
|
19
|
+
- You must wrap await calls in an async function and call it using `asyncio.run`.
|
|
20
|
+
- For displaying final results to the user, you must present your output in markdown format, including image links, so that they are rendered and displayed to the user. The code output is NOT visible to the user.
|
|
21
21
|
|
|
22
|
-
NOTE: If any function throws an error requiring authentication, provide the user with a Markdown link to the authentication page and prompt them to authenticate.
|
|
23
22
|
"""
|
|
24
23
|
import inspect
|
|
25
24
|
import re
|
|
@@ -27,6 +26,7 @@ from collections.abc import Sequence
|
|
|
27
26
|
from datetime import datetime
|
|
28
27
|
|
|
29
28
|
from langchain_core.tools import StructuredTool
|
|
29
|
+
from universal_mcp.agents.codeact0.utils import schema_to_signature
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def make_safe_function_name(name: str) -> str:
|
|
@@ -119,38 +119,49 @@ def indent(text, prefix, predicate=None):
|
|
|
119
119
|
return "".join(prefixed_lines)
|
|
120
120
|
|
|
121
121
|
|
|
122
|
+
|
|
122
123
|
def create_default_prompt(
|
|
123
124
|
tools: Sequence[StructuredTool],
|
|
125
|
+
additional_tools : Sequence[StructuredTool],
|
|
124
126
|
base_prompt: str | None = None,
|
|
125
127
|
):
|
|
126
|
-
system_prompt = uneditable_prompt.strip()
|
|
127
|
-
"\n\nIn addition to the Python Standard Library, you can use the following external functions
|
|
128
|
+
system_prompt = uneditable_prompt.strip() + (
|
|
129
|
+
"\n\nIn addition to the Python Standard Library, you can use the following external functions:"
|
|
130
|
+
"\n"
|
|
128
131
|
)
|
|
129
132
|
tools_context = {}
|
|
130
133
|
for tool in tools:
|
|
131
|
-
# Create a safe function name
|
|
132
|
-
safe_name = make_safe_function_name(tool.name)
|
|
133
|
-
# Use coroutine if it exists, otherwise use func
|
|
134
134
|
if hasattr(tool, "coroutine") and tool.coroutine is not None:
|
|
135
135
|
tool_callable = tool.coroutine
|
|
136
|
-
signature = inspect.signature(tool_callable)
|
|
137
136
|
is_async = True
|
|
138
137
|
elif hasattr(tool, "func") and tool.func is not None:
|
|
139
138
|
tool_callable = tool.func
|
|
140
|
-
signature = inspect.signature(tool_callable)
|
|
141
139
|
is_async = False
|
|
142
|
-
else:
|
|
143
|
-
|
|
140
|
+
system_prompt += f'''{"async " if is_async else ""}{schema_to_signature(tool.args, tool.name)}:
|
|
141
|
+
"""{tool.description}"""
|
|
142
|
+
...
|
|
143
|
+
'''
|
|
144
|
+
safe_name = make_safe_function_name(tool.name)
|
|
144
145
|
tools_context[safe_name] = tool_callable
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
|
|
147
|
+
for tool in additional_tools:
|
|
148
|
+
if hasattr(tool, "coroutine") and tool.coroutine is not None:
|
|
149
|
+
tool_callable = tool.coroutine
|
|
150
|
+
is_async = True
|
|
151
|
+
elif hasattr(tool, "func") and tool.func is not None:
|
|
152
|
+
tool_callable = tool.func
|
|
153
|
+
is_async = False
|
|
154
|
+
system_prompt += f'''{"async " if is_async else ""}def {tool.name} {str(inspect.signature(tool_callable))}:
|
|
155
|
+
"""{tool.description}"""
|
|
150
156
|
...
|
|
151
|
-
'''
|
|
152
|
-
|
|
157
|
+
'''
|
|
158
|
+
safe_name = make_safe_function_name(tool.name)
|
|
159
|
+
tools_context[safe_name] = tool_callable
|
|
160
|
+
|
|
153
161
|
if base_prompt and base_prompt.strip():
|
|
154
162
|
system_prompt += f"Your goal is to perform the following task:\n\n{base_prompt}"
|
|
155
163
|
|
|
156
164
|
return system_prompt, tools_context
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
base_prompt: 'Fetch all open issues from the GitHub repository "microsoft/vscode" and add them to a new Google Sheet. Then create corresponding tasks in ClickUp for each issue with descriptions, tags, and "In Progress" status. Delete processed rows from the sheet after creating ClickUp tasks.'
|
|
2
2
|
tools:
|
|
3
3
|
- google_sheet__get_values
|
|
4
|
+
- google_sheet__create_spreadsheet
|
|
5
|
+
- google_sheet__write_values_to_sheet
|
|
6
|
+
- google_sheet__delete_dimensions
|
|
7
|
+
- google_sheet__append_values
|
|
8
|
+
- google_sheet__update_values
|
|
4
9
|
- clickup__tasks_create_new_task
|
|
5
10
|
- clickup__spaces_get_details
|
|
6
11
|
- clickup__lists_get_list_details
|
|
7
12
|
- clickup__tasks_get_list_tasks
|
|
8
|
-
-
|
|
9
|
-
- google_sheet__update_values
|
|
10
|
-
- google_sheet__get_spreadsheet_metadata
|
|
11
|
-
- google_sheet__batch_get_values_by_range
|
|
12
|
-
- github__list_issues
|
|
13
|
+
- github__search_issues
|
|
13
14
|
- github__update_issue
|
|
@@ -3,8 +3,6 @@ from collections.abc import Sequence
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
5
|
from langchain_core.messages import BaseMessage
|
|
6
|
-
from pydantic import ValidationError
|
|
7
|
-
from requests import JSONDecodeError
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
def light_copy(data):
|
|
@@ -74,45 +72,6 @@ def make_safe_function_name(name: str) -> str:
|
|
|
74
72
|
safe_name = "unnamed_tool"
|
|
75
73
|
return safe_name
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
def filter_retry_on(exc: Exception) -> bool:
|
|
79
|
-
import httpx
|
|
80
|
-
import requests
|
|
81
|
-
|
|
82
|
-
if isinstance(
|
|
83
|
-
exc,
|
|
84
|
-
(
|
|
85
|
-
ConnectionError,
|
|
86
|
-
JSONDecodeError,
|
|
87
|
-
ValidationError,
|
|
88
|
-
),
|
|
89
|
-
):
|
|
90
|
-
return True
|
|
91
|
-
if isinstance(
|
|
92
|
-
exc,
|
|
93
|
-
(
|
|
94
|
-
ValueError,
|
|
95
|
-
TypeError,
|
|
96
|
-
ArithmeticError,
|
|
97
|
-
ImportError,
|
|
98
|
-
LookupError,
|
|
99
|
-
NameError,
|
|
100
|
-
SyntaxError,
|
|
101
|
-
RuntimeError,
|
|
102
|
-
ReferenceError,
|
|
103
|
-
StopIteration,
|
|
104
|
-
StopAsyncIteration,
|
|
105
|
-
OSError,
|
|
106
|
-
),
|
|
107
|
-
):
|
|
108
|
-
return False
|
|
109
|
-
if isinstance(exc, httpx.HTTPStatusError):
|
|
110
|
-
return 500 <= exc.response.status_code < 600
|
|
111
|
-
if isinstance(exc, requests.HTTPError):
|
|
112
|
-
return 500 <= exc.response.status_code < 600 if exc.response else True
|
|
113
|
-
return True
|
|
114
|
-
|
|
115
|
-
|
|
116
75
|
def derive_context(code: str, context: dict[str, Any]) -> dict[str, Any]:
|
|
117
76
|
"""
|
|
118
77
|
Derive context from code by extracting classes, functions, and import statements.
|
|
@@ -176,23 +135,20 @@ def derive_context(code: str, context: dict[str, Any]) -> dict[str, Any]:
|
|
|
176
135
|
|
|
177
136
|
if class_def not in context["classes"]:
|
|
178
137
|
context["classes"].append(class_def)
|
|
179
|
-
|
|
180
|
-
# Extract function definitions (
|
|
138
|
+
|
|
139
|
+
# Extract function definitions (including async)
|
|
181
140
|
for node in ast.walk(tree):
|
|
182
|
-
if isinstance(node, ast.FunctionDef):
|
|
183
|
-
# Get the function definition as a string
|
|
141
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
184
142
|
func_lines = code.split("\n")[node.lineno - 1 : node.end_lineno]
|
|
185
143
|
func_def = "\n".join(func_lines)
|
|
186
144
|
|
|
187
|
-
#
|
|
188
|
-
# Top-level functions should start at column 0 (no indentation)
|
|
145
|
+
# Only top-level functions (col_offset == 0)
|
|
189
146
|
if node.col_offset == 0:
|
|
190
|
-
# Clean up the function definition (remove leading/trailing whitespace)
|
|
191
147
|
func_def = func_def.strip()
|
|
192
|
-
|
|
193
148
|
if func_def not in context["functions"]:
|
|
194
149
|
context["functions"].append(func_def)
|
|
195
150
|
|
|
151
|
+
|
|
196
152
|
except SyntaxError:
|
|
197
153
|
# If the code has syntax errors, try a simpler regex-based approach
|
|
198
154
|
|
|
@@ -351,24 +307,47 @@ def inject_context(
|
|
|
351
307
|
exec(function_definition, namespace)
|
|
352
308
|
except Exception:
|
|
353
309
|
# If execution fails, try to extract function name and create placeholder
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
func_match = re.search(r"def\s+(\w+)", function_definition)
|
|
310
|
+
func_match = re.search(r"(async\s+)?def\s+(\w+)", function_definition)
|
|
357
311
|
if func_match:
|
|
358
|
-
func_name = func_match.group(
|
|
312
|
+
func_name = func_match.group(2)
|
|
313
|
+
is_async = bool(func_match.group(1))
|
|
359
314
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
315
|
+
if is_async:
|
|
316
|
+
async def placeholder_func(*args, **kwargs):
|
|
317
|
+
raise NotImplementedError(f"Async function '{func_name}' failed to load: {str(e)}")
|
|
318
|
+
else:
|
|
319
|
+
def placeholder_func(*args, **kwargs):
|
|
320
|
+
raise NotImplementedError(f"Function '{func_name}' failed to load: {str(e)}")
|
|
363
321
|
|
|
364
322
|
placeholder_func.__name__ = func_name
|
|
365
323
|
namespace[func_name] = placeholder_func
|
|
366
|
-
else:
|
|
367
|
-
# If we can't extract function name, create a generic placeholder
|
|
368
|
-
def generic_placeholder_func(*args, **kwargs):
|
|
369
|
-
raise NotImplementedError(f"Function definition failed to load: {str(e)}")
|
|
370
|
-
|
|
371
|
-
generic_placeholder_func.__name__ = f"func_{len(namespace)}"
|
|
372
|
-
namespace[generic_placeholder_func.__name__] = generic_placeholder_func
|
|
373
324
|
|
|
374
325
|
return namespace
|
|
326
|
+
|
|
327
|
+
def schema_to_signature(schema: dict, func_name="my_function") -> str:
|
|
328
|
+
type_map = {
|
|
329
|
+
"integer": "int",
|
|
330
|
+
"string": "str",
|
|
331
|
+
"boolean": "bool",
|
|
332
|
+
"null": "None",
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
params = []
|
|
336
|
+
for name, meta in schema.items():
|
|
337
|
+
# figure out type
|
|
338
|
+
if "type" in meta:
|
|
339
|
+
typ = type_map.get(meta["type"], "Any")
|
|
340
|
+
elif "anyOf" in meta:
|
|
341
|
+
types = [type_map.get(t["type"], "Any") for t in meta["anyOf"]]
|
|
342
|
+
typ = " | ".join(set(types))
|
|
343
|
+
else:
|
|
344
|
+
typ = "Any"
|
|
345
|
+
|
|
346
|
+
default = meta.get("default", None)
|
|
347
|
+
default_repr = repr(default)
|
|
348
|
+
|
|
349
|
+
params.append(f"{name}: {typ} = {default_repr}")
|
|
350
|
+
|
|
351
|
+
# join into signature
|
|
352
|
+
param_str = ",\n ".join(params)
|
|
353
|
+
return f"def {func_name}(\n {param_str},\n):"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from rich import print
|
|
4
|
+
from universal_mcp.agentr.registry import AgentrRegistry
|
|
5
|
+
from universal_mcp.agents.llm import load_chat_model
|
|
6
|
+
from universal_mcp.agents.shared.tool_node import build_tool_node_graph
|
|
7
|
+
from universal_mcp.logger import setup_logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def main():
|
|
11
|
+
"""
|
|
12
|
+
An example of how to run the tool_node graph independently.
|
|
13
|
+
"""
|
|
14
|
+
setup_logger(level="INFO")
|
|
15
|
+
|
|
16
|
+
user_input = "What are the topics of my meetings today from Google Calendar and who are the attendees? Give a 1-line context for each attendee using LinkedIn or web search."
|
|
17
|
+
|
|
18
|
+
print(f"▶️ User Task: [bold cyan]'{user_input}'[/bold cyan]\n")
|
|
19
|
+
|
|
20
|
+
llm = load_chat_model("azure/gpt-4.1", thinking=False)
|
|
21
|
+
registry = AgentrRegistry()
|
|
22
|
+
|
|
23
|
+
graph = build_tool_node_graph(llm=llm, registry=registry)
|
|
24
|
+
|
|
25
|
+
initial_state = {"original_task": user_input}
|
|
26
|
+
|
|
27
|
+
print("🚀 Invoking the tool selection graph...")
|
|
28
|
+
final_state = await graph.ainvoke(initial_state)
|
|
29
|
+
|
|
30
|
+
execution_plan = final_state.get("execution_plan")
|
|
31
|
+
|
|
32
|
+
print("\n[bold green]✅ Graph execution complete![/bold green]")
|
|
33
|
+
print("\n--- Final Execution Plan (Selected Tools) ---")
|
|
34
|
+
if execution_plan:
|
|
35
|
+
print(execution_plan)
|
|
36
|
+
else:
|
|
37
|
+
print("[bold red]No execution plan was created.[/bold red]")
|
|
38
|
+
if messages := final_state.get("messages"):
|
|
39
|
+
print(f"Final Message: {messages[-1].content}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
asyncio.run(main())
|
|
@@ -1,132 +1,83 @@
|
|
|
1
|
-
|
|
2
|
-
You are an expert
|
|
1
|
+
TOOL_SEARCH_QUERIES_PROMPT = """
|
|
2
|
+
You are an expert at breaking down a complex user task into a list of simple, atomic search queries for finding tools. Your goal is to generate a list of queries that will cover all aspects of the user's request.
|
|
3
3
|
|
|
4
4
|
**CORE PRINCIPLES:**
|
|
5
|
-
1. **
|
|
6
|
-
2. **
|
|
7
|
-
3. **
|
|
8
|
-
4. **
|
|
9
|
-
|
|
5
|
+
1. **Deconstruct the Task:** Analyze the user's request and identify the distinct actions or sub-tasks required.
|
|
6
|
+
2. **Include Application Context:** If the user mentions a specific application (e.g., Gmail, Google Docs, Exa), include it in the query.
|
|
7
|
+
3. **Focus on the Action:** Each query must describe a general capability. It should combine the core action (verb) and the general type of object it acts on (e.g., "create document", "get pull requests", "web search").
|
|
8
|
+
4. **STRIP SPECIFIC DETAILS:** **This is critical.** Do NOT include specific data, parameters, names, or details from the user's prompt in your queries. Your goal is to find a general tool, not to run the specific command.
|
|
9
|
+
|
|
10
10
|
**--- EXAMPLES ---**
|
|
11
11
|
|
|
12
12
|
**EXAMPLE 1:**
|
|
13
13
|
- **User Task:** "Create a Google Doc summarizing the last 5 merged pull requests in my GitHub repo universal-mcp/universal-mcp."
|
|
14
|
-
- **CORRECT
|
|
15
|
-
- "
|
|
16
|
-
- "
|
|
14
|
+
- **CORRECT QUERIES:**
|
|
15
|
+
- "github get pull requests from repository"
|
|
16
|
+
- "google docs create document"
|
|
17
|
+
- "google docs append text to document"
|
|
18
|
+
- **INCORRECT QUERIES:**
|
|
19
|
+
- "github get pull requests from universal-mcp/universal-mcp" (Contains specific repo name)
|
|
20
|
+
- "google docs create 'summary' document" (Contains specific document title)
|
|
21
|
+
|
|
17
22
|
|
|
18
23
|
**EXAMPLE 2:**
|
|
19
|
-
- **User Task:** "Find the best restaurants in Goa using
|
|
20
|
-
- **CORRECT
|
|
21
|
-
- "
|
|
24
|
+
- **User Task:** "Find the best restaurants in Goa using exa web search, then email the list to my friend at test@example.com."
|
|
25
|
+
- **CORRECT QUERIES:**
|
|
26
|
+
- "exa web search"
|
|
27
|
+
- "send email"
|
|
28
|
+
- **INCORRECT QUERIES:**
|
|
29
|
+
- "exa search for best restaurants in Goa" (Contains specific search details)
|
|
30
|
+
- "email list to test@example.com" (Contains specific parameters)
|
|
31
|
+
|
|
32
|
+
**EXAMPLE 3:**
|
|
33
|
+
- **User Task:** "add an event to my google calendar at 2pm called 'Walk in the park'?"
|
|
34
|
+
- **CORRECT QUERIES:**
|
|
35
|
+
- "google calendar create calendar event"
|
|
36
|
+
- **INCORRECT QUERIES:**
|
|
37
|
+
- "google calendar create event 'Walk in the park' at 2pm" (Contains specific event details)
|
|
22
38
|
|
|
23
39
|
**--- YOUR TASK ---**
|
|
24
40
|
|
|
25
41
|
**USER TASK:**
|
|
26
42
|
"{task}"
|
|
27
43
|
|
|
28
|
-
**YOUR
|
|
44
|
+
**YOUR SEARCH QUERIES (as a list of strings):**
|
|
29
45
|
"""
|
|
30
46
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
You are an expert at selecting an application to perform a specific sub-task. Your goal is to generate a concise query for an app search engine.
|
|
34
|
-
|
|
35
|
-
Analyze the current sub-task in the context of the original user goal and the ENTIRE PLAN so far.
|
|
36
|
-
|
|
37
|
-
**CORE INSTRUCTION:** If any application already used in the plan is capable of performing the current sub-task, your query MUST BE the name of that application to ensure continuity and efficiency. Otherwise, generate a concise query for the category of application needed.
|
|
38
|
-
|
|
39
|
-
**--- EXAMPLES ---**
|
|
40
|
-
|
|
41
|
-
**EXAMPLE 1: Reusing an app from two steps ago**
|
|
42
|
-
- **Original User Task:** "Find my latest order confirmation in Gmail, search for reviews of the main product on perplexity, and then send an email to ankit@agentr.dev telling about the reviews"
|
|
43
|
-
- **Plan So Far:**
|
|
44
|
-
- The sub-task 'Find order confirmation in Gmail' was assigned to app 'google_mail'.
|
|
45
|
-
- The sub-task 'Search for product reviews on perplexity' was assigned to app 'perplexity'.
|
|
46
|
-
- **Current Sub-task:** "send an email to ankit@agentr.dev"
|
|
47
|
-
- **CORRECT QUERY:** "google_mail"
|
|
48
|
-
|
|
49
|
-
**EXAMPLE 2: First Step (No previous context)**
|
|
50
|
-
- **Original User Task:** "Find the best restaurants in Goa."
|
|
51
|
-
- **Plan So Far:** None. This is the first step.
|
|
52
|
-
- **Current Sub-task:** "Perform a web search to find the best restaurants in Goa."
|
|
53
|
-
- **CORRECT QUERY:** "web search"
|
|
54
|
-
|
|
55
|
-
**--- YOUR TASK ---**
|
|
56
|
-
|
|
57
|
-
**Original User Task:**
|
|
58
|
-
"{original_task}"
|
|
59
|
-
|
|
60
|
-
**Plan So Far:**
|
|
61
|
-
{plan_context}
|
|
62
|
-
|
|
63
|
-
**Current Sub-task:**
|
|
64
|
-
"{sub_task}"
|
|
65
|
-
|
|
66
|
-
**YOUR CONCISE APP SEARCH QUERY:**
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
TOOL_SEARCH_QUERY_PROMPT = """
|
|
71
|
-
You are an expert at summarizing the core *action* of a sub-task into a concise query for finding a tool. This query should ignore any application names.
|
|
47
|
+
APP_SELECTION_PROMPT = """
|
|
48
|
+
You are an AI assistant that selects the most appropriate applications (apps) from a list to accomplish a user's task.
|
|
72
49
|
|
|
73
50
|
**INSTRUCTIONS:**
|
|
74
|
-
1.
|
|
75
|
-
2.
|
|
76
|
-
3.
|
|
77
|
-
4.
|
|
78
|
-
|
|
79
|
-
**EXAMPLES:**
|
|
80
|
-
- **Sub-task:** "Perform a web search using Perplexity to find the best restaurants in Goa."
|
|
81
|
-
- **Query:** "web search for restaurants"
|
|
82
|
-
|
|
83
|
-
- **Sub-task:** "Fetch all marketing emails received from Gmail in the last 7 days."
|
|
84
|
-
- **Query:** "get emails by date"
|
|
85
|
-
|
|
86
|
-
- **Sub-task:** "Create a new Google Doc and append a summary."
|
|
87
|
-
- **Query:** "create document, append text"
|
|
88
|
-
|
|
89
|
-
**SUB-TASK:**
|
|
90
|
-
"{sub_task}"
|
|
91
|
-
|
|
92
|
-
**YOUR CONCISE TOOL SEARCH QUERY:**
|
|
93
|
-
"""
|
|
94
|
-
|
|
95
|
-
REVISE_DECOMPOSITION_PROMPT = """
|
|
96
|
-
You are an expert planner who revises plans that have failed. Your previous attempt to break down a task resulted in a sub-task that could not be matched with any available tools.
|
|
97
|
-
|
|
98
|
-
**INSTRUCTIONS:**
|
|
99
|
-
1. Analyze the original user task and the failed sub-task.
|
|
100
|
-
2. Generate a NEW, alternative decomposition of the original task.
|
|
101
|
-
3. This new plan should try to achieve the same overall goal but with different, perhaps broader or more combined, sub-tasks to increase the chance of finding a suitable tool.
|
|
51
|
+
1. Carefully review the original user task to understand the complete goal.
|
|
52
|
+
2. Examine the list of available apps, their IDs, and their descriptions.
|
|
53
|
+
3. Select ALL app IDs that are necessary to complete the entire task.
|
|
54
|
+
4. If the user's task mentions a specific app, you MUST select it.
|
|
55
|
+
5. If no apps are a good fit, return an empty list.
|
|
102
56
|
|
|
103
57
|
**ORIGINAL USER TASK:**
|
|
104
58
|
"{task}"
|
|
105
59
|
|
|
106
|
-
**
|
|
107
|
-
|
|
60
|
+
**AVAILABLE APPS:**
|
|
61
|
+
{app_candidates}
|
|
108
62
|
|
|
109
|
-
**YOUR
|
|
63
|
+
**YOUR SELECTED APP ID(s) (as a list of strings):**
|
|
110
64
|
"""
|
|
111
65
|
|
|
112
|
-
|
|
113
66
|
TOOL_SELECTION_PROMPT = """
|
|
114
|
-
You are an AI assistant that selects the most appropriate tool(s) from a list to accomplish a
|
|
67
|
+
You are an AI assistant that selects the most appropriate tool(s) from a list to accomplish a user's overall task.
|
|
115
68
|
|
|
116
69
|
**INSTRUCTIONS:**
|
|
117
|
-
1. Carefully review the
|
|
118
|
-
2. Examine the list of available tools and their descriptions.
|
|
119
|
-
3. Select
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
5. Only return the tool IDs.
|
|
123
|
-
6. You should understand that the sub task maybe specific in nature but the tools are made to be general purpose and therefore the tool_candidates you will get will be very general purpose but that should not stop you from selecting the tools as these tools will be given to a very smart agent who will be able to use these tools for the specific sub-taks
|
|
70
|
+
1. Carefully review the original user task to understand the complete goal.
|
|
71
|
+
2. Examine the list of available tools, their IDs, and their descriptions. These tools have been found using a search based on the task.
|
|
72
|
+
3. Select all tool IDs that are necessary to complete the entire task. It is critical to select all tools needed for a multi-step task.
|
|
73
|
+
4. If no tools are a good fit for the task, return an empty list.
|
|
74
|
+
5. Only return the tool IDs. The tools are general purpose, but you are smart enough to see how they can be used for the specific task.
|
|
124
75
|
|
|
125
|
-
**
|
|
126
|
-
"{
|
|
76
|
+
**ORIGINAL USER TASK:**
|
|
77
|
+
"{task}"
|
|
127
78
|
|
|
128
79
|
**AVAILABLE TOOLS:**
|
|
129
80
|
{tool_candidates}
|
|
130
81
|
|
|
131
|
-
**YOUR SELECTED TOOL ID(s):**
|
|
132
|
-
"""
|
|
82
|
+
**YOUR SELECTED TOOL ID(s) (as a list of strings):**
|
|
83
|
+
"""
|