universal-mcp-agents 0.1.23rc8__py3-none-any.whl → 0.1.23rc10__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.

@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  import base64
3
- import json
4
3
  from collections import defaultdict
5
4
  from pathlib import Path
6
5
  from typing import Annotated, Any
@@ -26,181 +25,279 @@ def create_meta_tools(tool_registry: AgentrRegistry) -> dict[str, Any]:
26
25
  @tool
27
26
  async def search_functions(
28
27
  queries: Annotated[
29
- list[str] | str | None,
30
- Field(description="A single query or a list of queries to search for relevant functions"),
28
+ list[list[str]] | None,
29
+ Field(
30
+ description="A list of query lists. Each inner list contains one or more search terms that will be used together to find relevant tools."
31
+ ),
31
32
  ] = None,
32
- app_id: Annotated[
33
- str | None,
34
- Field(description="The ID or common name of a specific application to search within"),
33
+ app_ids: Annotated[
34
+ list[str] | None,
35
+ Field(description="The ID or list of IDs (common names) of specific applications to search within."),
35
36
  ] = None,
36
37
  ) -> str:
37
38
  """
38
- Searches for relevant functions across applications based on queries and/or a specific app.
39
- This function operates in three modes:
40
-
41
- 1. **Global Search (provide `queries` only):**
42
- - Use when the user wants to perform an action without specifying an application.
43
- - The system will search across all available functions.
44
- - Example: For "how can I create a presentation?", call with queries=["create presentation"].
45
-
46
- 2. **App Discovery (provide `app_id` only):**
47
- - Use when the user asks about the capabilities of a specific application.
48
- - The `app_id` can be the common name of the app (e.g., "Gmail", "Google Drive").
49
- - This will return all available functions for that application, up to a limit.
50
- - Example: For "what can you do with Gmail?", call with app_id="Gmail".
51
-
52
- 3. **Scoped Search (provide `queries` AND `app_id`):**
53
- - Use when the user wants to perform an action within a specific application.
54
- - This performs a targeted search only within the specified app's functions.
55
- - Example: For "how do I find an email in Gmail?", call with queries=["find email"], app_id="Gmail".
39
+ Searches for relevant functions based on queries and/or applications. This function
40
+ operates in three powerful modes with support for multi-query searches:
41
+
42
+ 1. **Global Search** (`queries` only as List[List[str]]):
43
+ - Searches all functions across all applications.
44
+ - Supports multiple independent searches in parallel.
45
+ - Each inner list represents a separate search query.
46
+
47
+ Examples:
48
+ - Single global search:
49
+ `search_functions(queries=[["create presentation"]])`
50
+
51
+ - Multiple independent global searches:
52
+ `search_functions(queries=[["send email"], ["schedule meeting"]])`
53
+
54
+ - Multi-term search for comprehensive results:
55
+ `search_functions(queries=[["send email", "draft email", "compose email"]])`
56
+
57
+ 2. **App Discovery** (`app_ids` only as List[str]):
58
+ - Returns ALL available functions for one or more specific applications.
59
+ - Use this to explore the complete capability set of an application.
60
+
61
+ Examples:
62
+ - Single app discovery:
63
+ `search_functions(app_ids=["Gmail"])`
64
+
65
+ - Multiple app discovery:
66
+ `search_functions(app_ids=["Gmail", "Google Calendar", "Slack"])`
67
+
68
+ 3. **Scoped Search** (`queries` as List[List[str]] and `app_ids` as List[str]):
69
+ - Performs targeted searches within specific applications in parallel.
70
+ - The number of app_ids must match the number of inner query lists.
71
+ - Each query list is searched within its corresponding app_id.
72
+ - Supports multiple search terms per app for comprehensive discovery.
73
+
74
+ Examples:
75
+ - Basic scoped search (one query per app):
76
+ `search_functions(queries=[["find email"], ["share file"]], app_ids=["Gmail", "Google_Drive"])`
77
+
78
+ - Multi-term scoped search (multiple queries per app):
79
+ `search_functions(
80
+ queries=[
81
+ ["send email", "draft email", "compose email", "reply to email"],
82
+ ["create event", "schedule meeting", "find free time"],
83
+ ["upload file", "share file", "create folder", "search files"]
84
+ ],
85
+ app_ids=["Gmail", "Google Calendar", "Google_Drive"]
86
+ )`
87
+
88
+ - Mixed complexity (some apps with single query, others with multiple):
89
+ `search_functions(
90
+ queries=[
91
+ ["list messages"],
92
+ ["create event", "delete event", "update event"]
93
+ ],
94
+ app_ids=["Gmail", "Google Calendar"]
95
+ )`
96
+
97
+ **Pro Tips:**
98
+ - Use multiple search terms in a single query list to cast a wider net and discover related functionality
99
+ - Multi-term searches are more efficient than separate calls
100
+ - Scoped searches return more focused results than global searches
101
+ - The function returns connection status for each app (connected vs NOT connected)
102
+ - All searches within a single call execute in parallel for maximum efficiency
103
+
104
+ **Parameters:**
105
+ - `queries` (List[List[str]], optional): A list of query lists. Each inner list contains one or more
106
+ search terms that will be used together to find relevant tools.
107
+ - `app_ids` (List[str], optional): A list of application IDs to search within or discover.
108
+
109
+ **Returns:**
110
+ - A structured response containing:
111
+ - Matched tools with their descriptions
112
+ - Connection status for each app
113
+ - Recommendations for which tools to load next
56
114
  """
57
- if isinstance(queries, str): # Handle JSON string input
58
- try:
59
- queries = json.loads(queries)
60
- except json.JSONDecodeError:
61
- # If it's a single query as a string, convert to list
62
- queries = [queries] if queries else None
115
+ registry = tool_registry
63
116
 
64
- if not queries and not app_id:
65
- raise ValueError("You must provide 'queries', an 'app_id', or both.")
117
+ TOOL_THRESHOLD = 0.75
118
+ APP_THRESHOLD = 0.7
66
119
 
67
- registry = tool_registry
68
- connections = await registry.list_connected_apps()
69
- connected_app_ids = {connection["app_id"] for connection in connections}
120
+ # --- Helper Functions for Different Search Modes ---
121
+
122
+ async def _handle_global_search(queries: list[str]) -> list[list[dict[str, Any]]]:
123
+ """Performs a broad search across all apps to find relevant tools and apps."""
124
+ # 1. Perform initial broad searches for tools and apps concurrently.
125
+ initial_tool_tasks = [registry.search_tools(query=q, distance_threshold=TOOL_THRESHOLD) for q in queries]
126
+ app_search_tasks = [registry.search_apps(query=q, distance_threshold=APP_THRESHOLD) for q in queries]
70
127
 
71
- canonical_app_id = None
72
- found_tools_result = []
73
- THRESHOLD = 0.8
74
-
75
- if app_id:
76
- relevant_apps = await registry.search_apps(query=app_id, distance_threshold=THRESHOLD)
77
- if not relevant_apps:
78
- return {
79
- "found_tools": [],
80
- "message": f"Search failed. Application '{app_id}' was not found.",
81
- }
82
- canonical_app_id = relevant_apps[0]["id"]
83
-
84
- if canonical_app_id and not queries:
85
- all_app_tools = await registry.search_tools(query="", app_id=canonical_app_id, limit=20)
86
-
87
- tool_list = []
88
- for tool in all_app_tools:
89
- cleaned_description = tool.get("description", "").split("Context:")[0].strip()
90
- tool_list.append({"id": tool["id"], "description": cleaned_description})
91
-
92
- found_tools_result.append(
93
- {
94
- "app_id": canonical_app_id,
95
- "connection_status": "connected" if canonical_app_id in connected_app_ids else "not_connected",
96
- "tools": tool_list,
97
- }
128
+ initial_tool_results, app_search_results = await asyncio.gather(
129
+ asyncio.gather(*initial_tool_tasks), asyncio.gather(*app_search_tasks)
98
130
  )
99
131
 
100
- else:
101
- query_results = []
102
- prioritized_app_id_list = []
132
+ # 2. Create a prioritized list of app IDs for the final search.
133
+ app_ids_from_apps = {app["id"] for result_list in app_search_results for app in result_list}
134
+ prioritized_app_id_list = list(app_ids_from_apps)
135
+
136
+ app_ids_from_tools = {tool["app_id"] for result_list in initial_tool_results for tool in result_list}
137
+ for tool_app_id in app_ids_from_tools:
138
+ if tool_app_id not in app_ids_from_apps:
139
+ prioritized_app_id_list.append(tool_app_id)
140
+
141
+ if not prioritized_app_id_list:
142
+ return []
143
+
144
+ # 3. Perform the final, comprehensive tool search across the prioritized apps.
145
+ final_tool_search_tasks = [
146
+ registry.search_tools(query=query, app_id=app_id_to_search, distance_threshold=TOOL_THRESHOLD)
147
+ for app_id_to_search in prioritized_app_id_list
148
+ for query in queries
149
+ ]
150
+ return await asyncio.gather(*final_tool_search_tasks)
151
+
152
+ async def _handle_scoped_search(app_ids: list[str], queries: list[list[str]]) -> list[list[dict[str, Any]]]:
153
+ """Performs targeted searches for specific queries within specific applications."""
154
+ if len(app_ids) != len(queries):
155
+ raise ValueError("The number of app_ids must match the number of query lists.")
156
+
157
+ tasks = []
158
+ for app_id, query_list in zip(app_ids, queries):
159
+ for query in query_list:
160
+ # Create a search task for each query in the list for the corresponding app
161
+ tasks.append(registry.search_tools(query=query, app_id=app_id, distance_threshold=TOOL_THRESHOLD))
162
+
163
+ return await asyncio.gather(*tasks)
164
+
165
+ async def _handle_app_discovery(app_ids: list[str]) -> list[list[dict[str, Any]]]:
166
+ """Fetches all tools for a list of applications."""
167
+ tasks = [registry.search_tools(query="", app_id=app_id, limit=20) for app_id in app_ids]
168
+ return await asyncio.gather(*tasks)
169
+
170
+ # --- Helper Functions for Structuring and Formatting Results ---
171
+
172
+ def _format_response(structured_results: list[dict[str, Any]]) -> str:
173
+ """Builds the final, user-facing formatted string response from structured data."""
174
+ if not structured_results:
175
+ return "No relevant functions were found."
176
+
177
+ result_parts = []
178
+ apps_in_results = {app["app_id"] for app in structured_results}
179
+ connected_apps_in_results = {
180
+ app["app_id"] for app in structured_results if app["connection_status"] == "connected"
181
+ }
103
182
 
104
- if canonical_app_id:
105
- prioritized_app_id_list = [canonical_app_id]
106
- else:
107
- # 1. Perform an initial broad search for tools.
108
- initial_tool_search_tasks = [
109
- registry.search_tools(query=q, distance_threshold=THRESHOLD) for q in queries
110
- ]
111
- initial_tool_results = await asyncio.gather(*initial_tool_search_tasks)
112
-
113
- # 2. Search for relevant apps.
114
- app_search_tasks = [registry.search_apps(query=q, distance_threshold=THRESHOLD) for q in queries]
115
- app_search_results = await asyncio.gather(*app_search_tasks)
116
-
117
- # 3. Create a prioritized list of app IDs for the final search.
118
- # Apps found via search_apps are considered higher priority and come first.
119
- app_ids_from_apps = {app["id"] for result_list in app_search_results for app in result_list}
120
- # Use a list to maintain order.
121
- prioritized_app_id_list.extend(list(app_ids_from_apps))
122
-
123
- # Add app_ids from the initial tool search, ensuring no duplicates.
124
- app_ids_from_tools = {tool["app_id"] for result_list in initial_tool_results for tool in result_list}
125
-
126
- for tool_app_id in app_ids_from_tools:
127
- if tool_app_id not in app_ids_from_apps:
128
- prioritized_app_id_list.append(tool_app_id)
129
-
130
- # 4. Perform the final, comprehensive tool search across the prioritized list of apps.
131
- if prioritized_app_id_list:
132
- # print(f"Prioritized app IDs for final search: {prioritized_app_id_list}")
133
- final_tool_search_tasks = []
134
- for app_id_to_search in prioritized_app_id_list:
135
- for query in queries:
136
- final_tool_search_tasks.append(
137
- registry.search_tools(query=query, app_id=app_id_to_search, distance_threshold=THRESHOLD)
138
- )
139
- query_results = await asyncio.gather(*final_tool_search_tasks)
140
-
141
- # 5. Aggregate all found tools for easy lookup.
183
+ for app in structured_results:
184
+ app_id = app["app_id"]
185
+ app_status = "connected" if app["connection_status"] == "connected" else "NOT connected"
186
+ result_parts.append(f"Tools from {app_id} (status: {app_status} by user):")
187
+
188
+ for tool in app["tools"]:
189
+ result_parts.append(f" - {tool['id']}: {tool['description']}")
190
+ result_parts.append("") # Empty line for readability
191
+
192
+ # Add summary connection status messages
193
+ if not connected_apps_in_results and len(apps_in_results) > 1:
194
+ result_parts.append(
195
+ "Connection Status: None of the apps in the results are connected. "
196
+ "You must ask the user to choose the application."
197
+ )
198
+ elif len(connected_apps_in_results) > 1:
199
+ connected_list = ", ".join(sorted(list(connected_apps_in_results)))
200
+ result_parts.append(
201
+ f"Connection Status: Multiple apps are connected ({connected_list}). "
202
+ "You must ask the user to select which application they want to use."
203
+ )
204
+
205
+ result_parts.append("Call load_functions to select the required functions only.")
206
+ if 0 < len(connected_apps_in_results) < len(apps_in_results):
207
+ result_parts.append(
208
+ "Unconnected app functions can also be loaded if required by the user, "
209
+ "but prefer connected ones. Ask the user to choose if none of the "
210
+ "relevant apps are connected."
211
+ )
212
+
213
+ return "\n".join(result_parts)
214
+
215
+ def _structure_tool_results(
216
+ raw_tool_lists: list[list[dict[str, Any]]], connected_app_ids: set[str]
217
+ ) -> list[dict[str, Any]]:
218
+ """
219
+ Converts raw search results into a structured format, handling duplicates,
220
+ cleaning descriptions, and adding connection status.
221
+ """
142
222
  aggregated_tools = defaultdict(dict)
143
- for tool_list in query_results:
223
+ # Use a list to maintain the order of apps as they are found.
224
+ ordered_app_ids = []
225
+
226
+ for tool_list in raw_tool_lists:
144
227
  for tool in tool_list:
145
- app_id_from_tool = tool.get("app_id", "unknown")
228
+ app_id = tool.get("app_id", "unknown")
146
229
  tool_id = tool.get("id")
147
- if not tool_id or tool_id in aggregated_tools[app_id_from_tool]:
230
+
231
+ if not tool_id:
148
232
  continue
149
- cleaned_description = tool.get("description", "").split("Context:")[0].strip()
150
- aggregated_tools[app_id_from_tool][tool_id] = {
151
- "id": tool_id,
152
- "description": cleaned_description,
153
- }
154
-
155
- # 6. Build the final results list, respecting the prioritized app order.
156
- for app_id_from_list in prioritized_app_id_list:
157
- if app_id_from_list in aggregated_tools and aggregated_tools[app_id_from_list]:
233
+
234
+ if app_id not in aggregated_tools:
235
+ ordered_app_ids.append(app_id)
236
+
237
+ if tool_id not in aggregated_tools[app_id]:
238
+ aggregated_tools[app_id][tool_id] = {
239
+ "id": tool_id,
240
+ "description": _clean_tool_description(tool.get("description", "")),
241
+ }
242
+
243
+ # Build the final results list respecting the discovery order.
244
+ found_tools_result = []
245
+ for app_id in ordered_app_ids:
246
+ if app_id in aggregated_tools and aggregated_tools[app_id]:
158
247
  found_tools_result.append(
159
248
  {
160
- "app_id": app_id_from_list,
161
- "connection_status": "connected"
162
- if app_id_from_list in connected_app_ids
163
- else "not_connected",
164
- "tools": list(aggregated_tools[app_id_from_list].values()),
249
+ "app_id": app_id,
250
+ "connection_status": "connected" if app_id in connected_app_ids else "not_connected",
251
+ "tools": list(aggregated_tools[app_id].values()),
165
252
  }
166
253
  )
254
+ return found_tools_result
167
255
 
168
- # Build result string efficiently
169
- result_parts = []
170
- apps_in_results = {app["app_id"] for app in found_tools_result}
171
- connected_apps_in_results = apps_in_results.intersection(connected_app_ids)
172
-
173
- for app in found_tools_result:
174
- app_id = app["app_id"]
175
- connection_status = app["connection_status"]
176
- tools = app["tools"]
177
-
178
- app_status = "connected" if connection_status == "connected" else "NOT connected"
179
- result_parts.append(f"Tools from {app_id} (status: {app_status} by user):")
180
-
181
- for tool in tools:
182
- tool_id = tool["id"]
183
- description = tool["description"]
184
- result_parts.append(f" - {tool_id}: {description}")
185
- result_parts.append("") # Empty line between apps
186
-
187
- # Add connection status information
188
- if len(connected_apps_in_results) == 0 and len(apps_in_results) > 1:
189
- result_parts.append(
190
- "Connection Status: None of the apps in the results are connected. You must ask the user to choose the application."
191
- )
192
- elif len(connected_apps_in_results) > 1:
193
- connected_list = ", ".join(connected_apps_in_results)
194
- result_parts.append(
195
- f"Connection Status: Multiple apps are connected ({connected_list}). You must ask the user to select which application they want to use."
196
- )
256
+ def _clean_tool_description(description: str) -> str:
257
+ """Consistently formats tool descriptions by removing implementation details."""
258
+ return description.split("Context:")[0].strip()
259
+
260
+ # Main Function Logic
197
261
 
198
- result_parts.append("Call load_functions to select the required functions only.")
199
- if len(connected_apps_in_results) < len(apps_in_results) and len(connected_apps_in_results) > 0:
200
- result_parts.append(
201
- "Unconnected app functions can also be loaded if required by the user, but prefer connected ones. And do ask the user to choose if none of the relevant apps are connected"
262
+ if not queries and not app_ids:
263
+ raise ValueError("You must provide 'queries', 'app_ids', or both.")
264
+
265
+ # --- Initialization and Input Normalization ---
266
+ connections = await registry.list_connected_apps()
267
+ connected_app_ids = {connection["app_id"] for connection in connections}
268
+
269
+ canonical_app_ids = []
270
+ if app_ids:
271
+ # Concurrently search for all provided app names
272
+ app_search_tasks = [
273
+ registry.search_apps(query=app_name, distance_threshold=APP_THRESHOLD) for app_name in app_ids
274
+ ]
275
+ app_search_results = await asyncio.gather(*app_search_tasks)
276
+
277
+ # Process results and build the list of canonical IDs, handling not found errors
278
+ for app_name, result_list in zip(app_ids, app_search_results):
279
+ if not result_list:
280
+ raise ValueError(f"Application '{app_name}' could not be found.")
281
+ # Assume the first result is the correct one
282
+ canonical_app_ids.append(result_list[0]["id"])
283
+
284
+ # --- Mode Dispatching ---
285
+ raw_results = []
286
+
287
+ if canonical_app_ids and queries:
288
+ raw_results = await _handle_scoped_search(canonical_app_ids, queries)
289
+ elif canonical_app_ids:
290
+ raw_results = await _handle_app_discovery(canonical_app_ids)
291
+ elif queries:
292
+ # Flatten list of lists to list of strings for global search
293
+ flat_queries = (
294
+ [q for sublist in queries for q in sublist] if queries and not isinstance(queries[0], str) else queries
202
295
  )
203
- return "\n".join(result_parts)
296
+ raw_results = await _handle_global_search(flat_queries)
297
+
298
+ # --- Structuring and Formatting ---
299
+ structured_data = _structure_tool_results(raw_results, connected_app_ids)
300
+ return _format_response(structured_data)
204
301
 
205
302
  @tool
206
303
  async def load_functions(tool_ids: list[str]) -> str:
@@ -4,6 +4,7 @@ from langchain_anthropic import ChatAnthropic
4
4
  from langchain_core.language_models import BaseChatModel
5
5
  from langchain_google_genai import ChatGoogleGenerativeAI
6
6
  from langchain_openai import AzureChatOpenAI
7
+ from loguru import logger
7
8
 
8
9
 
9
10
  @lru_cache(maxsize=8)
@@ -41,8 +42,6 @@ def load_chat_model(
41
42
 
42
43
 
43
44
  if __name__ == "__main__":
44
- from loguru import logger
45
-
46
45
  models_to_test = [
47
46
  "azure/gpt-5-chat",
48
47
  "anthropic/claude-4-sonnet-20250514",
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  from typing import Any, Literal, cast
3
3
 
4
+ from langchain.agents import create_agent
4
5
  from pydantic import BaseModel, Field
5
6
  from universal_mcp.applications.application import BaseApplication
6
7
 
@@ -37,7 +38,7 @@ class LlmApp(BaseApplication):
37
38
  """Initialize the LLMApp."""
38
39
  super().__init__(name="llm")
39
40
 
40
- async def generate_text(
41
+ def generate_text(
41
42
  self,
42
43
  task: str,
43
44
  context: str | list[str] | dict[str, str] = "",
@@ -91,10 +92,10 @@ class LlmApp(BaseApplication):
91
92
  full_prompt = f"{prompt}\n\nContext:\n{context_str}\n\n"
92
93
 
93
94
  model = load_chat_model("azure/gpt-5-mini")
94
- response = await model.with_retry(stop_after_attempt=MAX_RETRIES).ainvoke(full_prompt)
95
+ response = model.with_retry(stop_after_attempt=MAX_RETRIES).invoke(full_prompt, stream=False)
95
96
  return str(response.content)
96
97
 
97
- async def classify_data(
98
+ def classify_data(
98
99
  self,
99
100
  classification_task_and_requirements: str,
100
101
  context: Any | list[Any] | dict[str, Any],
@@ -150,24 +151,24 @@ class LlmApp(BaseApplication):
150
151
  f"This is a classification task.\nPossible classes and descriptions:\n"
151
152
  f"{json.dumps(class_descriptions, indent=2)}\n\n"
152
153
  f"Context:\n{context_str}\n\n"
153
- "Return ONLY a valid JSON object, no extra text."
154
154
  )
155
155
 
156
- model = load_chat_model("azure/gpt-5-mini", temperature=0)
157
-
158
156
  class ClassificationResult(BaseModel):
159
157
  probabilities: dict[str, float] = Field(..., description="The probabilities for each class.")
160
158
  reason: str = Field(..., description="The reasoning behind the classification.")
161
159
  top_class: str = Field(..., description="The class with the highest probability.")
162
160
 
163
- response = (
164
- await model.with_structured_output(schema=ClassificationResult)
165
- .with_retry(stop_after_attempt=MAX_RETRIES)
166
- .ainvoke(prompt)
161
+ model = load_chat_model("azure/gpt-5-mini", temperature=0)
162
+ agent = create_agent(
163
+ model=model,
164
+ tools=[],
165
+ response_format=ClassificationResult, # Auto-selects ProviderStrategy
167
166
  )
168
- return response.model_dump()
169
167
 
170
- async def extract_data(
168
+ result = agent.invoke({"messages": [{"role": "user", "content": prompt}]}, stream=False)
169
+ return result["structured_response"].model_dump()
170
+
171
+ def extract_data(
171
172
  self,
172
173
  extraction_task: str,
173
174
  source: Any | list[Any] | dict[str, Any],
@@ -230,14 +231,14 @@ class LlmApp(BaseApplication):
230
231
 
231
232
  model = load_chat_model("azure/gpt-5-mini", temperature=0)
232
233
 
233
- response = await (
234
+ response = (
234
235
  model.with_structured_output(schema=output_schema, method="json_mode")
235
236
  .with_retry(stop_after_attempt=MAX_RETRIES)
236
- .ainvoke(prompt)
237
+ .invoke(prompt, stream=False)
237
238
  )
238
239
  return cast(dict[str, Any], response)
239
240
 
240
- async def call_llm(
241
+ def call_llm(
241
242
  self,
242
243
  task_instructions: str,
243
244
  context: Any | list[Any] | dict[str, Any],
@@ -283,12 +284,13 @@ class LlmApp(BaseApplication):
283
284
 
284
285
  model = load_chat_model("azure/gpt-5-mini", temperature=0)
285
286
 
286
- response = await (
287
- model.with_structured_output(schema=output_schema)
288
- .with_retry(stop_after_attempt=MAX_RETRIES)
289
- .ainvoke(prompt)
287
+ agent = create_agent(
288
+ model=model,
289
+ tools=[],
290
+ response_format=output_schema,
290
291
  )
291
- return response.model_dump()
292
+ result = agent.invoke({"messages": [{"role": "user", "content": prompt}]}, stream=False)
293
+ return result["structured_response"]
292
294
 
293
295
  def list_tools(self):
294
296
  return [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp-agents
3
- Version: 0.1.23rc8
3
+ Version: 0.1.23rc10
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
@@ -2,7 +2,7 @@ universal_mcp/agents/__init__.py,sha256=Ythw8tyq7p-w1SPnuO2JtS4TvYEP75PkQpdyvZv-
2
2
  universal_mcp/agents/base.py,sha256=IyU1HUmB8rjHuCxv-c29RV-dWXfdiQiPq5rGkcCiSbU,7833
3
3
  universal_mcp/agents/cli.py,sha256=9CG7majpWUz7C6t0d8xr-Sg2ZPKBuQdykTbYS6KIZ3A,922
4
4
  universal_mcp/agents/hil.py,sha256=_5PCK6q0goGm8qylJq44aSp2MadP-yCPvhOJYKqWLMo,3808
5
- universal_mcp/agents/llm.py,sha256=hVRwjZs3MHl5_3BWedmurs2Jt1oZDfFX0Zj9F8KH7fk,1787
5
+ universal_mcp/agents/llm.py,sha256=S6dI3xaeVS8rKa2ttXToOYf_mI-6rm0E9XwE5nm3uko,1782
6
6
  universal_mcp/agents/react.py,sha256=ocYm94HOiJVI2zwTjO1K2PNfVY7EILLJ6cd__jnGHPs,3327
7
7
  universal_mcp/agents/sandbox.py,sha256=YxTGp_zsajuN7FUn0Q4PFjuXczgLht7oKql_gyb2Gf4,5112
8
8
  universal_mcp/agents/simple.py,sha256=NSATg5TWzsRNS7V3LFiDG28WSOCIwCdcC1g7NRwg2nM,2095
@@ -29,7 +29,7 @@ universal_mcp/agents/codeact0/llm_tool.py,sha256=-pAz04OrbZ_dJ2ueysT1qZd02DrbLY4
29
29
  universal_mcp/agents/codeact0/prompts.py,sha256=RiC_43GSeE4LDoiFhmJIOsKkoijOK9_7skwAH6ZqSWk,15501
30
30
  universal_mcp/agents/codeact0/sandbox.py,sha256=Zcr7fvYtcGbwNWd7RPV7-Btl2HtycPIPofEGVmzxSmE,4696
31
31
  universal_mcp/agents/codeact0/state.py,sha256=cf-94hfVub-HSQJk6b7_SzqBS-oxMABjFa8jqyjdDK0,1925
32
- universal_mcp/agents/codeact0/tools.py,sha256=kWFlEfJdbOPugPMbsP7I-vxFMGfZj3FUilMI-aT7-Xw,18753
32
+ universal_mcp/agents/codeact0/tools.py,sha256=oaGBzto6yaysPPEwV0bpAHH8QASjEaTIey_zJHxmNyY,23182
33
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
@@ -37,8 +37,8 @@ universal_mcp/agents/shared/tool_node.py,sha256=DC9F-Ri28Pam0u3sXWNODVgmj9PtAEUb
37
37
  universal_mcp/applications/filesystem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  universal_mcp/applications/filesystem/app.py,sha256=0TRjjm8YnslVRSmfkXI7qQOAlqWlD1eEn8Jm0xBeigs,5561
39
39
  universal_mcp/applications/llm/__init__.py,sha256=_XGRxN3O1--ZS5joAsPf8IlI9Qa6negsJrwJ5VJXno0,46
40
- universal_mcp/applications/llm/app.py,sha256=zcMCcswJxvbk2jN_x4xpiv2IG2yoJ62jH1CQaNltmYs,12645
40
+ universal_mcp/applications/llm/app.py,sha256=y84p2gIIsgTG5A1fxtZCPUAr3k4qacg8bp2jntBxy8A,12761
41
41
  universal_mcp/applications/ui/app.py,sha256=c7OkZsO2fRtndgAzAQbKu-1xXRuRp9Kjgml57YD2NR4,9459
42
- universal_mcp_agents-0.1.23rc8.dist-info/METADATA,sha256=q1hNCeGDqDDTJ5TWkQkh8JuHoXyNgNZEbDOXViM-xM0,931
43
- universal_mcp_agents-0.1.23rc8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
44
- universal_mcp_agents-0.1.23rc8.dist-info/RECORD,,
42
+ universal_mcp_agents-0.1.23rc10.dist-info/METADATA,sha256=J2tqsjN2LrLGdhyhjLGKwQHrXWenn2D4rH5Mqp20QWc,932
43
+ universal_mcp_agents-0.1.23rc10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
44
+ universal_mcp_agents-0.1.23rc10.dist-info/RECORD,,