universal-mcp-agents 0.1.13__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/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/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 +156 -177
- universal_mcp/agents/utils.py +65 -0
- universal_mcp/applications/ui/app.py +2 -2
- {universal_mcp_agents-0.1.13.dist-info → universal_mcp_agents-0.1.14.dist-info}/METADATA +1 -1
- {universal_mcp_agents-0.1.13.dist-info → universal_mcp_agents-0.1.14.dist-info}/RECORD +25 -22
- {universal_mcp_agents-0.1.13.dist-info → universal_mcp_agents-0.1.14.dist-info}/WHEEL +0 -0
|
@@ -1,227 +1,206 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections import defaultdict
|
|
1
3
|
from typing import Annotated, TypedDict
|
|
2
4
|
|
|
3
5
|
from langchain_core.language_models import BaseChatModel
|
|
4
6
|
from langchain_core.messages import AIMessage, AnyMessage
|
|
5
7
|
from langgraph.graph import END, StateGraph
|
|
6
8
|
from langgraph.graph.message import add_messages
|
|
9
|
+
from langgraph.types import Command
|
|
7
10
|
from loguru import logger
|
|
8
11
|
from pydantic import BaseModel, Field
|
|
9
12
|
from universal_mcp.tools.registry import ToolRegistry
|
|
13
|
+
from universal_mcp.types import ToolConfig
|
|
10
14
|
|
|
11
15
|
from universal_mcp.agents.shared.prompts import (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
TASK_DECOMPOSITION_PROMPT,
|
|
15
|
-
TOOL_SEARCH_QUERY_PROMPT,
|
|
16
|
+
APP_SELECTION_PROMPT,
|
|
17
|
+
TOOL_SEARCH_QUERIES_PROMPT,
|
|
16
18
|
TOOL_SELECTION_PROMPT,
|
|
17
19
|
)
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
MAX_RETRIES = 1
|
|
20
22
|
|
|
21
|
-
# --- Pydantic Models for Structured LLM Outputs ---
|
|
22
23
|
|
|
24
|
+
class SearchQueries(BaseModel):
|
|
25
|
+
queries: list[str] = Field(description="A list of search queries for finding tools.")
|
|
23
26
|
|
|
24
|
-
class TaskDecomposition(BaseModel):
|
|
25
|
-
sub_tasks: list[str] = Field(description="A list of sub-task descriptions.")
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
query: str = Field(description="A concise search query.")
|
|
28
|
+
class AppSelection(BaseModel):
|
|
29
|
+
app_ids: list[str] = Field(description="The IDs of the selected applications.")
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class ToolSelection(BaseModel):
|
|
33
33
|
tool_ids: list[str] = Field(description="The IDs of the selected tools.")
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
class AgentState(TypedDict):
|
|
37
|
+
"""The central state of our agent graph."""
|
|
37
38
|
|
|
39
|
+
original_task: str
|
|
40
|
+
queries: list[str]
|
|
41
|
+
candidate_tools: list[dict]
|
|
42
|
+
execution_plan: ToolConfig
|
|
43
|
+
messages: Annotated[list[AnyMessage], add_messages]
|
|
44
|
+
retry_count: int
|
|
38
45
|
|
|
39
|
-
class SubTask(TypedDict, total=False):
|
|
40
|
-
"""Represents a single step in the execution plan."""
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
app_id: str
|
|
45
|
-
tool_ids: list[str]
|
|
46
|
-
reasoning: str
|
|
47
|
+
def build_tool_node_graph(llm: BaseChatModel, registry: ToolRegistry) -> StateGraph:
|
|
48
|
+
"""Builds a workflow for tool selection with a retry mechanism."""
|
|
47
49
|
|
|
50
|
+
async def _search_for_tools(state: AgentState) -> Command:
|
|
51
|
+
"""
|
|
52
|
+
Performs a hierarchical search:
|
|
53
|
+
1. Generates search queries for the task.
|
|
54
|
+
2. Searches for candidate *applications*.
|
|
55
|
+
3. Uses an LLM to select the most relevant applications.
|
|
56
|
+
4. Searches for tools only within the selected applications.
|
|
57
|
+
If any step fails, it can trigger a retry.
|
|
58
|
+
"""
|
|
59
|
+
task = state["original_task"]
|
|
60
|
+
logger.info(f"Starting hierarchical tool search for task: '{task}'")
|
|
61
|
+
|
|
62
|
+
prompt = TOOL_SEARCH_QUERIES_PROMPT.format(task=task)
|
|
63
|
+
response = await llm.with_structured_output(SearchQueries).ainvoke(prompt)
|
|
64
|
+
queries = response.queries
|
|
65
|
+
logger.info(f"Generated search queries: {queries}")
|
|
66
|
+
|
|
67
|
+
if not queries:
|
|
68
|
+
logger.error("LLM failed to generate any search queries.")
|
|
69
|
+
return Command(
|
|
70
|
+
update={"messages": [AIMessage(content="I could not understand the task to search for tools.")]},
|
|
71
|
+
goto="handle_failure",
|
|
72
|
+
)
|
|
48
73
|
|
|
49
|
-
|
|
50
|
-
|
|
74
|
+
# Always store queries for potential retry
|
|
75
|
+
update_state = {"queries": queries}
|
|
51
76
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
sub_tasks: list[SubTask]
|
|
56
|
-
execution_plan: list[SubTask]
|
|
57
|
-
messages: Annotated[list[AnyMessage], add_messages]
|
|
77
|
+
app_search_tasks = [registry.search_apps(query, distance_threshold=0.7) for query in queries]
|
|
78
|
+
app_results = await asyncio.gather(*app_search_tasks)
|
|
79
|
+
unique_apps = {app["id"]: app for app_list in app_results for app in app_list}
|
|
58
80
|
|
|
81
|
+
if not unique_apps:
|
|
82
|
+
logger.warning(f"No applications found for queries: {queries}. Triggering retry.")
|
|
83
|
+
return Command(update=update_state, goto="general_search_and_select")
|
|
59
84
|
|
|
60
|
-
|
|
85
|
+
logger.info(f"Found {len(unique_apps)} candidate applications.")
|
|
61
86
|
|
|
87
|
+
app_candidates_str = "\n - ".join([f"{app['id']}: {app['description']}" for app in unique_apps.values()])
|
|
88
|
+
app_selection_prompt = APP_SELECTION_PROMPT.format(task=task, app_candidates=app_candidates_str)
|
|
89
|
+
app_selection_response = await llm.with_structured_output(AppSelection).ainvoke(app_selection_prompt)
|
|
90
|
+
selected_app_ids = app_selection_response.app_ids
|
|
62
91
|
|
|
63
|
-
|
|
64
|
-
|
|
92
|
+
if not selected_app_ids:
|
|
93
|
+
logger.warning("LLM did not select any applications from the candidate list. Triggering retry.")
|
|
94
|
+
return Command(update=update_state, goto="general_search_and_select")
|
|
65
95
|
|
|
66
|
-
|
|
67
|
-
"""Decomposes the main task or revises a failed decomposition."""
|
|
68
|
-
attempts = state.get("decomposition_attempts", 0)
|
|
69
|
-
task = state["original_task"]
|
|
70
|
-
failed_info = state.get("failed_sub_task_info")
|
|
96
|
+
logger.success(f"Selected {len(selected_app_ids)} applications: {selected_app_ids}")
|
|
71
97
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
"sub_tasks": sub_tasks,
|
|
84
|
-
"decomposition_attempts": attempts + 1,
|
|
85
|
-
"messages": [AIMessage(content=f"New plan created with {len(sub_tasks)} steps.")],
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async def _resolve_sub_tasks(state: AgentState) -> AgentState:
|
|
89
|
-
"""Iterates through sub-tasks, providing full plan context to the app selection prompt."""
|
|
90
|
-
sub_tasks = state["sub_tasks"]
|
|
91
|
-
original_task = state["original_task"]
|
|
92
|
-
current_plan = []
|
|
93
|
-
|
|
94
|
-
for i, sub_task in enumerate(sub_tasks):
|
|
95
|
-
task_desc = sub_task["task"]
|
|
96
|
-
logger.info(f"Resolving sub-task: '{task_desc}'")
|
|
97
|
-
|
|
98
|
-
# 1. Build the FULL context string from the entire plan so far
|
|
99
|
-
if not current_plan:
|
|
100
|
-
plan_context_str = "None. This is the first step."
|
|
101
|
-
else:
|
|
102
|
-
context_lines = [
|
|
103
|
-
f"- The sub-task '{step['task']}' was assigned to app '{step['app_id']}'." for step in current_plan
|
|
104
|
-
]
|
|
105
|
-
plan_context_str = "\n".join(context_lines)
|
|
106
|
-
|
|
107
|
-
# 2. Generate the App-specific query using the NEW full-context prompt
|
|
108
|
-
app_query_prompt = APP_SEARCH_QUERY_PROMPT.format(
|
|
109
|
-
original_task=original_task, plan_context=plan_context_str, sub_task=task_desc
|
|
110
|
-
)
|
|
111
|
-
app_query_response = await llm.with_structured_output(SearchQuery).ainvoke(app_query_prompt)
|
|
112
|
-
app_search_query = app_query_response.query
|
|
113
|
-
logger.info(f"Generated context-aware app search query: '{app_search_query}'")
|
|
114
|
-
|
|
115
|
-
# 3. Search for candidate apps (the rest of the logic is the same)
|
|
116
|
-
candidate_apps = await registry.search_apps(query=app_search_query, limit=5)
|
|
117
|
-
if not candidate_apps:
|
|
118
|
-
logger.error(f"No apps found for query '{app_search_query}' from sub-task: '{task_desc}'")
|
|
119
|
-
return {"failed_sub_task_info": task_desc, "sub_tasks": []}
|
|
120
|
-
|
|
121
|
-
# 4. Generate Action-specific query for finding the tool
|
|
122
|
-
tool_query_prompt = TOOL_SEARCH_QUERY_PROMPT.format(sub_task=task_desc)
|
|
123
|
-
tool_query_response = await llm.with_structured_output(SearchQuery).ainvoke(tool_query_prompt)
|
|
124
|
-
tool_search_query = tool_query_response.query
|
|
125
|
-
logger.info(f"Generated tool search query: '{tool_search_query}'")
|
|
126
|
-
|
|
127
|
-
# 5. Find a suitable tool within the candidate apps
|
|
128
|
-
tool_found = False
|
|
129
|
-
for app in candidate_apps:
|
|
130
|
-
app_id = app["id"]
|
|
131
|
-
logger.info(f"Searching for tools in app '{app_id}' with query '{tool_search_query}'...")
|
|
132
|
-
|
|
133
|
-
found_tools = await registry.search_tools(query=tool_search_query, app_id=app_id, limit=5)
|
|
134
|
-
if not found_tools:
|
|
135
|
-
continue
|
|
136
|
-
|
|
137
|
-
tool_candidates_str = "\n - ".join([f"{tool['name']}: {tool['description']}" for tool in found_tools])
|
|
138
|
-
selection_prompt = TOOL_SELECTION_PROMPT.format(sub_task=task_desc, tool_candidates=tool_candidates_str)
|
|
139
|
-
selection_response = await llm.with_structured_output(ToolSelection).ainvoke(selection_prompt)
|
|
140
|
-
|
|
141
|
-
if selection_response.tool_ids:
|
|
142
|
-
logger.success(f"Found and selected tool(s) {selection_response.tool_ids} in app '{app_id}'.")
|
|
143
|
-
sub_task.update(
|
|
144
|
-
{
|
|
145
|
-
"status": "success",
|
|
146
|
-
"app_id": app_id,
|
|
147
|
-
"tool_ids": selection_response.tool_ids,
|
|
148
|
-
"reasoning": f"Selected tool(s) {selection_response.tool_ids} from app '{app_id}' for sub-task.",
|
|
149
|
-
}
|
|
150
|
-
)
|
|
151
|
-
current_plan.append(sub_task)
|
|
152
|
-
tool_found = True
|
|
153
|
-
break
|
|
154
|
-
|
|
155
|
-
if not tool_found:
|
|
156
|
-
logger.error(f"Could not find any suitable tool for sub-task: '{task_desc}'")
|
|
157
|
-
return {"failed_sub_task_info": task_desc, "sub_tasks": []}
|
|
158
|
-
|
|
159
|
-
return {"execution_plan": current_plan, "sub_tasks": []}
|
|
160
|
-
|
|
161
|
-
def _handle_planning_failure(state: AgentState) -> AgentState:
|
|
162
|
-
"""Handles the case where all decomposition attempts have failed."""
|
|
163
|
-
logger.error("Maximum decomposition attempts reached. Planning failed.")
|
|
164
|
-
return {
|
|
165
|
-
"messages": [
|
|
166
|
-
AIMessage(
|
|
167
|
-
content="I am unable to create a complete plan for this task with the available tools. Please try rephrasing your request."
|
|
168
|
-
)
|
|
169
|
-
]
|
|
170
|
-
}
|
|
98
|
+
tool_search_tasks = [
|
|
99
|
+
registry.search_tools(task, app_id=app_id, distance_threshold=0.8) for app_id in selected_app_ids
|
|
100
|
+
]
|
|
101
|
+
tool_results = await asyncio.gather(*tool_search_tasks)
|
|
102
|
+
candidate_tools = [tool for tool_list in tool_results for tool in tool_list]
|
|
103
|
+
|
|
104
|
+
if not candidate_tools:
|
|
105
|
+
logger.warning(f"No tools found within the selected applications: {selected_app_ids}. Triggering retry.")
|
|
106
|
+
return Command(update=update_state, goto="general_search_and_select")
|
|
171
107
|
|
|
172
|
-
|
|
108
|
+
logger.success(f"Found {len(candidate_tools)} candidate tools from selected apps.")
|
|
109
|
+
update_state["candidate_tools"] = candidate_tools
|
|
110
|
+
return Command(update=update_state, goto="select_tools_for_plan")
|
|
111
|
+
|
|
112
|
+
async def _general_search_and_select(state: AgentState) -> Command:
|
|
173
113
|
"""
|
|
174
|
-
|
|
175
|
-
It combines their tool_ids into a single unique list.
|
|
114
|
+
A retry node that performs a general tool search without app filters.
|
|
176
115
|
"""
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
else:
|
|
188
|
-
# If app already seen, just update its set of tool_ids
|
|
189
|
-
merged_apps[app_id]["tool_ids"].update(step["tool_ids"])
|
|
116
|
+
task = state["original_task"]
|
|
117
|
+
queries = state["queries"]
|
|
118
|
+
retry_count = state.get("retry_count", 0)
|
|
119
|
+
|
|
120
|
+
if retry_count >= MAX_RETRIES:
|
|
121
|
+
logger.error("Max retries reached. Failing the planning process.")
|
|
122
|
+
return Command(
|
|
123
|
+
update={"messages": [AIMessage(content="I could not find any relevant tools after extensive searching.")]},
|
|
124
|
+
goto="handle_failure",
|
|
125
|
+
)
|
|
190
126
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
127
|
+
logger.info(f"--- RETRY {retry_count + 1}/{MAX_RETRIES} ---")
|
|
128
|
+
logger.info("Performing a general tool search without app filters.")
|
|
129
|
+
|
|
130
|
+
general_search_tasks = [
|
|
131
|
+
registry.search_tools(query, distance_threshold=0.85) for query in queries
|
|
132
|
+
]
|
|
133
|
+
tool_results = await asyncio.gather(*general_search_tasks)
|
|
134
|
+
|
|
135
|
+
unique_tools = {tool['id']: tool for tool_list in tool_results for tool in tool_list}
|
|
136
|
+
candidate_tools = list(unique_tools.values())
|
|
137
|
+
|
|
138
|
+
if not candidate_tools:
|
|
139
|
+
logger.error("General search (retry) also failed to find any tools.")
|
|
140
|
+
return Command(
|
|
141
|
+
update={
|
|
142
|
+
"messages": [AIMessage(content="I could not find any tools for your request, even with a broader search.")],
|
|
143
|
+
"retry_count": retry_count + 1,
|
|
144
|
+
},
|
|
145
|
+
goto="handle_failure",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
logger.success(f"General search found {len(candidate_tools)} candidate tools.")
|
|
149
|
+
return Command(
|
|
150
|
+
update={"candidate_tools": candidate_tools, "retry_count": retry_count + 1},
|
|
151
|
+
goto="select_tools_for_plan",
|
|
152
|
+
)
|
|
196
153
|
|
|
197
|
-
|
|
154
|
+
async def _select_tools_for_plan(state: AgentState) -> Command:
|
|
155
|
+
"""Selects the best tools from the candidates and builds the final execution plan."""
|
|
156
|
+
task = state["original_task"]
|
|
157
|
+
candidate_tools = state["candidate_tools"]
|
|
158
|
+
retry_count = state.get("retry_count", 0)
|
|
159
|
+
logger.info("Starting tool selection from candidate list.")
|
|
160
|
+
|
|
161
|
+
tool_candidates_str = "\n - ".join([f"{tool['id']}: {tool['description']}" for tool in candidate_tools])
|
|
162
|
+
prompt = TOOL_SELECTION_PROMPT.format(task=task, tool_candidates=tool_candidates_str)
|
|
163
|
+
response = await llm.with_structured_output(ToolSelection).ainvoke(prompt)
|
|
164
|
+
selected_tool_ids = response.tool_ids
|
|
165
|
+
|
|
166
|
+
if not selected_tool_ids:
|
|
167
|
+
if retry_count >= MAX_RETRIES:
|
|
168
|
+
logger.error("LLM did not select any tools, even after a retry. Failing.")
|
|
169
|
+
return Command(
|
|
170
|
+
update={"messages": [AIMessage(content="I found potential tools, but could not create a final plan.")]},
|
|
171
|
+
goto="handle_failure",
|
|
172
|
+
)
|
|
173
|
+
else:
|
|
174
|
+
logger.warning("LLM did not select any tools from the current candidate list. Triggering general search.")
|
|
175
|
+
return Command(goto="general_search_and_select")
|
|
198
176
|
|
|
199
|
-
# --- Graph Definition ---
|
|
200
177
|
|
|
201
|
-
|
|
178
|
+
logger.success(f"Selected {len(selected_tool_ids)} tools for the final plan: {selected_tool_ids}")
|
|
202
179
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
180
|
+
final_plan = defaultdict(list)
|
|
181
|
+
for tool_id in selected_tool_ids:
|
|
182
|
+
if "__" in tool_id:
|
|
183
|
+
app_id, tool_name = tool_id.split("__", 1)
|
|
184
|
+
final_plan[app_id].append(tool_name)
|
|
207
185
|
|
|
208
|
-
|
|
186
|
+
sorted_final_plan = {app_id: sorted(tools) for app_id, tools in final_plan.items()}
|
|
187
|
+
return Command(update={"execution_plan": sorted_final_plan}, goto=END)
|
|
209
188
|
|
|
210
|
-
def
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return "handle_planning_failure"
|
|
216
|
-
else:
|
|
217
|
-
return "decompose_task" # Re-try decomposition
|
|
189
|
+
def _handle_planning_failure(state: AgentState) -> Command:
|
|
190
|
+
"""Handles cases where tool search or selection fails by logging the final error message."""
|
|
191
|
+
if messages := state.get("messages"):
|
|
192
|
+
last_message = messages[-1].content
|
|
193
|
+
logger.error(f"Planning failed. Final message: {last_message}")
|
|
218
194
|
else:
|
|
219
|
-
|
|
195
|
+
logger.error("Planning failed with no specific message.")
|
|
196
|
+
return Command(goto=END)
|
|
220
197
|
|
|
221
|
-
workflow
|
|
222
|
-
workflow.
|
|
198
|
+
workflow = StateGraph(AgentState)
|
|
199
|
+
workflow.add_node("search_for_tools", _search_for_tools)
|
|
200
|
+
workflow.add_node("general_search_and_select", _general_search_and_select)
|
|
201
|
+
workflow.add_node("select_tools_for_plan", _select_tools_for_plan)
|
|
202
|
+
workflow.add_node("handle_failure", _handle_planning_failure)
|
|
223
203
|
|
|
224
|
-
workflow.
|
|
225
|
-
workflow.add_edge("handle_planning_failure", END)
|
|
204
|
+
workflow.set_entry_point("search_for_tools")
|
|
226
205
|
|
|
227
|
-
return workflow.compile()
|
|
206
|
+
return workflow.compile()
|
universal_mcp/agents/utils.py
CHANGED
|
@@ -9,6 +9,8 @@ from rich.markdown import Markdown
|
|
|
9
9
|
from rich.panel import Panel
|
|
10
10
|
from rich.prompt import Prompt
|
|
11
11
|
from rich.table import Table
|
|
12
|
+
from pydantic import ValidationError
|
|
13
|
+
from requests import JSONDecodeError
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class RichCLI:
|
|
@@ -143,3 +145,66 @@ def get_message_text(message: BaseMessage):
|
|
|
143
145
|
logger.error(f"Error getting message text: {e}")
|
|
144
146
|
logger.error(f"Message: {message}")
|
|
145
147
|
raise e
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def filter_retry_on(exc: Exception) -> bool:
|
|
151
|
+
import httpx
|
|
152
|
+
import requests
|
|
153
|
+
|
|
154
|
+
# Transient local/network issues and parsing hiccups
|
|
155
|
+
if isinstance(
|
|
156
|
+
exc,
|
|
157
|
+
(
|
|
158
|
+
TimeoutError,
|
|
159
|
+
ConnectionError,
|
|
160
|
+
JSONDecodeError,
|
|
161
|
+
ValidationError,
|
|
162
|
+
),
|
|
163
|
+
):
|
|
164
|
+
return True
|
|
165
|
+
|
|
166
|
+
# httpx transient request-layer errors
|
|
167
|
+
if isinstance(
|
|
168
|
+
exc,
|
|
169
|
+
(
|
|
170
|
+
httpx.TimeoutException,
|
|
171
|
+
httpx.ConnectError,
|
|
172
|
+
httpx.ReadError,
|
|
173
|
+
),
|
|
174
|
+
):
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
if isinstance(exc, (requests.Timeout, requests.ConnectionError)):
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
# HTTP status based retries: 408 (timeout), 429 (rate limit), and 5xx
|
|
181
|
+
if isinstance(exc, httpx.HTTPStatusError):
|
|
182
|
+
status = exc.response.status_code
|
|
183
|
+
return status == 408 or status == 429 or 500 <= status < 600
|
|
184
|
+
if isinstance(exc, requests.HTTPError):
|
|
185
|
+
if exc.response is None:
|
|
186
|
+
return True
|
|
187
|
+
status = exc.response.status_code
|
|
188
|
+
return status == 408 or status == 429 or 500 <= status < 600
|
|
189
|
+
|
|
190
|
+
if isinstance(
|
|
191
|
+
exc,
|
|
192
|
+
(
|
|
193
|
+
ValueError,
|
|
194
|
+
TypeError,
|
|
195
|
+
ArithmeticError,
|
|
196
|
+
ImportError,
|
|
197
|
+
LookupError,
|
|
198
|
+
NameError,
|
|
199
|
+
SyntaxError,
|
|
200
|
+
RuntimeError,
|
|
201
|
+
ReferenceError,
|
|
202
|
+
StopIteration,
|
|
203
|
+
StopAsyncIteration,
|
|
204
|
+
OSError,
|
|
205
|
+
),
|
|
206
|
+
):
|
|
207
|
+
return False
|
|
208
|
+
|
|
209
|
+
# Default: do not retry unknown exceptions
|
|
210
|
+
return False
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from typing import Any, Literal, TypedDict
|
|
3
3
|
|
|
4
|
-
from dotenv import load_dotenv
|
|
5
4
|
import httpx
|
|
6
|
-
from
|
|
5
|
+
from dotenv import load_dotenv
|
|
7
6
|
from markitdown import MarkItDown
|
|
7
|
+
from universal_mcp.applications.application import BaseApplication
|
|
8
8
|
|
|
9
9
|
load_dotenv()
|
|
10
10
|
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
universal_mcp/agents/__init__.py,sha256=kM4mC6Pf6lmaaZF1volo7VtKgA8FDyzb1sNenpB7Ulk,1244
|
|
2
|
-
universal_mcp/agents/base.py,sha256=
|
|
2
|
+
universal_mcp/agents/base.py,sha256=xQ_zMHTvULaiE4LiE7IfhrLmlBtvJ5zVE9Rq2nJ4Hm4,6976
|
|
3
3
|
universal_mcp/agents/cli.py,sha256=AG9e4iSX3GazT537573YrYT1wSaZYOr42rrYQ7xP3YA,1016
|
|
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
|
|
7
7
|
universal_mcp/agents/simple.py,sha256=NSATg5TWzsRNS7V3LFiDG28WSOCIwCdcC1g7NRwg2nM,2095
|
|
8
|
-
universal_mcp/agents/utils.py,sha256=
|
|
9
|
-
universal_mcp/agents/bigtool/__init__.py,sha256=
|
|
8
|
+
universal_mcp/agents/utils.py,sha256=pZFAqH__csH-gRLlTOnuU8kNix3_t5eC3yIk926FWpQ,6898
|
|
9
|
+
universal_mcp/agents/bigtool/__init__.py,sha256=mZG8dsaCVyKlm82otxtiTA225GIFLUCUUYPEIPF24uw,2299
|
|
10
10
|
universal_mcp/agents/bigtool/__main__.py,sha256=a15OUoqPR938x7eseWtxu0aLX7lRS3nu8k5Ks3giUY4,472
|
|
11
|
-
universal_mcp/agents/bigtool/agent.py,sha256=
|
|
11
|
+
universal_mcp/agents/bigtool/agent.py,sha256=FsWa7S00RHmzrLd2ms_aR4wJ09h_KFJ8DDzFnADKJOQ,251
|
|
12
12
|
universal_mcp/agents/bigtool/context.py,sha256=ny7gd-vvVpUOYAeQbAEUT0A6Vm6Nn2qGywxTzPBzYFg,929
|
|
13
|
-
universal_mcp/agents/bigtool/graph.py,sha256=
|
|
14
|
-
universal_mcp/agents/bigtool/prompts.py,sha256=
|
|
13
|
+
universal_mcp/agents/bigtool/graph.py,sha256=SE1IavwrSciIWsb_miekEh3H46UJWpeIOkcp7CsRG_w,5764
|
|
14
|
+
universal_mcp/agents/bigtool/prompts.py,sha256=Joi5mCzZX63aM_6eBrMOKuNRHjTkceVIibSsGBGqhYE,2041
|
|
15
15
|
universal_mcp/agents/bigtool/state.py,sha256=TQeGZD99okclkoCh5oz-VYIlEsC9yLQyDpnBnm7QCN8,759
|
|
16
|
-
universal_mcp/agents/bigtool/tools.py,sha256=
|
|
17
|
-
universal_mcp/agents/builder/__main__.py,sha256=
|
|
18
|
-
universal_mcp/agents/builder/builder.py,sha256=
|
|
19
|
-
universal_mcp/agents/builder/
|
|
20
|
-
universal_mcp/agents/builder/
|
|
16
|
+
universal_mcp/agents/bigtool/tools.py,sha256=vlydQQnITS7OPaSzyILf3czrgO3_fEB-i7zItKcEWjU,6377
|
|
17
|
+
universal_mcp/agents/builder/__main__.py,sha256=mGw5xHK53qNGgs6q7s2PPPPWozWPDKXD2AZGQTqCDQc,10750
|
|
18
|
+
universal_mcp/agents/builder/builder.py,sha256=uhUOH6S7F2iWDhpNOpfzVUT1VB0MQnmcEkLPYJs65gY,8151
|
|
19
|
+
universal_mcp/agents/builder/helper.py,sha256=G6IoXB433zAwfOGhDXOIGsf_EC9bbOSGN8WPWGSzmrk,2700
|
|
20
|
+
universal_mcp/agents/builder/prompts.py,sha256=k0IfhWZ10zou3I2e3kczxf2zpY2AsPshjeY_h5oQ1KA,2673
|
|
21
|
+
universal_mcp/agents/builder/state.py,sha256=nJrH-ChSpqv-fsXhvP4UkouVdQw0ji376uFM0aJ91D0,921
|
|
21
22
|
universal_mcp/agents/codeact/__init__.py,sha256=rLE8gvOo5H4YSr71DRq76b3RV3uuotxuAy_VnBVaVwk,60
|
|
22
23
|
universal_mcp/agents/codeact/__main__.py,sha256=W2cHXRwH1dZG3ETIkMwUqA_d62K3IctHP-FDZWDjxdw,1067
|
|
23
24
|
universal_mcp/agents/codeact/agent.py,sha256=sKZWokTHcuL68Y6SNyaaHe6_XkWxaIq36TrNmPJfQto,9762
|
|
@@ -28,16 +29,17 @@ universal_mcp/agents/codeact/state.py,sha256=WTPfpxDlGRnlr5tZuXMg_KU7GS7TZbnrIKs
|
|
|
28
29
|
universal_mcp/agents/codeact/utils.py,sha256=JUbT_HYGS_D1BzmzoVpORIe7SGur1KgJguTZ_1tZ4JY,1918
|
|
29
30
|
universal_mcp/agents/codeact0/__init__.py,sha256=rLE8gvOo5H4YSr71DRq76b3RV3uuotxuAy_VnBVaVwk,60
|
|
30
31
|
universal_mcp/agents/codeact0/__main__.py,sha256=V2wLWW9ym3rtiSvPEs-N0Mki7G5dYHzV5dAsAoF-ygQ,1148
|
|
31
|
-
universal_mcp/agents/codeact0/agent.py,sha256=
|
|
32
|
+
universal_mcp/agents/codeact0/agent.py,sha256=WmCHhhGN1q2Q1s14FN2kaNBEIVlYRoMpWJOqQEYjaBo,6538
|
|
32
33
|
universal_mcp/agents/codeact0/config.py,sha256=H-1woj_nhSDwf15F63WYn723y4qlRefXzGxuH81uYF0,2215
|
|
33
|
-
universal_mcp/agents/codeact0/
|
|
34
|
-
universal_mcp/agents/codeact0/
|
|
34
|
+
universal_mcp/agents/codeact0/langgraph_agent.py,sha256=8-Y3-OOEpY_XLcnjvFX-KyAQA5SzBjya3YBrrV9Qh_8,652
|
|
35
|
+
universal_mcp/agents/codeact0/llm_tool.py,sha256=9xgRylNZo3lya33L3iqS7yipJYaHCeUEEBCfjUp71vc,13821
|
|
36
|
+
universal_mcp/agents/codeact0/prompts.py,sha256=VT3v61JkAvZwjQoxwKR3QsYdkbyd_1E8bIm3oABNfMg,7512
|
|
35
37
|
universal_mcp/agents/codeact0/sandbox.py,sha256=tkrhQoV7sAIT5rtd5kpNYEgDz4y82WSHKpMGE6bqthE,3108
|
|
36
38
|
universal_mcp/agents/codeact0/state.py,sha256=kcoCoz-kd0Ukw1YNGDHNggixCK34KzMl2t6GztakSo4,412
|
|
37
|
-
universal_mcp/agents/codeact0/utils.py,sha256=
|
|
39
|
+
universal_mcp/agents/codeact0/utils.py,sha256=6yeUVsB2CkcrutHjfpMI3KsMJkVpFj3Piag6A2IG0Jc,14733
|
|
38
40
|
universal_mcp/agents/codeact0/usecases/1-unsubscribe.yaml,sha256=DiChHW-mNOcaaiec7f_04_A0Xyf9a2ihzXiPA9-Fw1I,239
|
|
39
41
|
universal_mcp/agents/codeact0/usecases/10-reddit2.yaml,sha256=R3vrZZNv_E-m_SuSJ5tSv11HqMomm8Gtxp_LDhkrnAw,618
|
|
40
|
-
universal_mcp/agents/codeact0/usecases/11-github.yaml,sha256=
|
|
42
|
+
universal_mcp/agents/codeact0/usecases/11-github.yaml,sha256=iu4-vlvopPct9vfAseElLXPUAWN5sMVri4oMbDYlA4s,672
|
|
41
43
|
universal_mcp/agents/codeact0/usecases/2-reddit.yaml,sha256=-dstvFMjsuP9UQM3B_G1HBn7ImNIMxSCNbITpfwgwAY,2525
|
|
42
44
|
universal_mcp/agents/codeact0/usecases/2.1-instructions.md,sha256=0gQBY_A3jT_lgKNiu6GdyEYupbX0Xz2unlCoTQwv-Es,5098
|
|
43
45
|
universal_mcp/agents/codeact0/usecases/2.2-instructions.md,sha256=Cx-VkcC55MrgFxlMBMBCD83jEge_yZgBWKwtuK1OPFc,4458
|
|
@@ -53,11 +55,12 @@ universal_mcp/agents/planner/__main__.py,sha256=OfhTfYDZK_ZUfc8sX-Sa6TWk-dNqD2rl
|
|
|
53
55
|
universal_mcp/agents/planner/graph.py,sha256=70hhIoEZOcYojpiyVSCedgYpnmxVP7aqdn8s6VBu-D4,3228
|
|
54
56
|
universal_mcp/agents/planner/prompts.py,sha256=_JoHqiAvswtqCDu90AGUHmfsu8eWE1-_yI4LLn3pqMU,657
|
|
55
57
|
universal_mcp/agents/planner/state.py,sha256=qqyp-jSGsCxe1US-PRLT4-y1sITAcVE6nCMlQLnvop0,278
|
|
56
|
-
universal_mcp/agents/shared/
|
|
57
|
-
universal_mcp/agents/shared/
|
|
58
|
+
universal_mcp/agents/shared/__main__.py,sha256=u6ezelmT7poaMRr5AfKp3uXGOV7zEwj017j_iAeUcZs,1450
|
|
59
|
+
universal_mcp/agents/shared/prompts.py,sha256=-gNyfhrJFtPEG4YeoJRYKmkmRHsI8a5f5ssxk_AWrOI,3760
|
|
60
|
+
universal_mcp/agents/shared/tool_node.py,sha256=syydbbKFMpu9Gg45yGP-kpN5KvD2oSRXWtqo1YpD6gY,9038
|
|
58
61
|
universal_mcp/applications/llm/__init__.py,sha256=xnpxq4Wl_pevvwtSUtEwcty8_d61ywO1V2YnEXyCREY,46
|
|
59
62
|
universal_mcp/applications/llm/app.py,sha256=iNLU6z2LRZc01GfSKvV0vNzT1LhKAjq_UrSJEmjthjw,6032
|
|
60
|
-
universal_mcp/applications/ui/app.py,sha256=
|
|
61
|
-
universal_mcp_agents-0.1.
|
|
62
|
-
universal_mcp_agents-0.1.
|
|
63
|
-
universal_mcp_agents-0.1.
|
|
63
|
+
universal_mcp/applications/ui/app.py,sha256=c7OkZsO2fRtndgAzAQbKu-1xXRuRp9Kjgml57YD2NR4,9459
|
|
64
|
+
universal_mcp_agents-0.1.14.dist-info/METADATA,sha256=6a7VjzkIfQdp6b4UfQVAMKFzg2qE8wszgkocVRgqNok,878
|
|
65
|
+
universal_mcp_agents-0.1.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
66
|
+
universal_mcp_agents-0.1.14.dist-info/RECORD,,
|
|
File without changes
|