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
|
@@ -4,12 +4,82 @@ import re
|
|
|
4
4
|
from collections.abc import Sequence
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
-
from langchain_core.messages import BaseMessage
|
|
7
|
+
from langchain_core.messages import AIMessage, BaseMessage
|
|
8
8
|
from universal_mcp.types import ToolConfig
|
|
9
9
|
|
|
10
10
|
MAX_CHARS = 5000
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def build_anthropic_cache_message(text: str, role: str = "system", ttl: str = "1h") -> list[dict[str, Any]]:
|
|
14
|
+
"""Build a complete Anthropic cache messages array from text.
|
|
15
|
+
|
|
16
|
+
Returns a list with a single cache message whose content is the
|
|
17
|
+
cached Anthropic content array with ephemeral cache control and TTL.
|
|
18
|
+
"""
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
"role": role,
|
|
22
|
+
"content": [
|
|
23
|
+
{
|
|
24
|
+
"type": "text",
|
|
25
|
+
"text": text,
|
|
26
|
+
"cache_control": {"type": "ephemeral", "ttl": ttl},
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
def strip_thinking(messages: list[BaseMessage]):
|
|
33
|
+
"""Remove Anthropic 'thinking' segments from the most recent AIMessage in-place.
|
|
34
|
+
|
|
35
|
+
Scans from the end to find the last AIMessage, then removes thinking blocks
|
|
36
|
+
from its content. Handles both plain-string and block-array content.
|
|
37
|
+
"""
|
|
38
|
+
if not messages:
|
|
39
|
+
return messages
|
|
40
|
+
|
|
41
|
+
# Find the last AIMessage from the end
|
|
42
|
+
last_ai_index = None
|
|
43
|
+
for i in range(len(messages) - 1, -1, -1):
|
|
44
|
+
if isinstance(messages[i], AIMessage):
|
|
45
|
+
last_ai_index = i
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
if last_ai_index is None:
|
|
49
|
+
return messages
|
|
50
|
+
|
|
51
|
+
ai_msg = messages[last_ai_index]
|
|
52
|
+
content = ai_msg.content
|
|
53
|
+
|
|
54
|
+
# If it's already plain text, nothing to strip
|
|
55
|
+
if isinstance(content, str):
|
|
56
|
+
return messages
|
|
57
|
+
|
|
58
|
+
# If Anthropic-style content blocks
|
|
59
|
+
if isinstance(content, list):
|
|
60
|
+
filtered_output: list[object] = []
|
|
61
|
+
removed_any = False
|
|
62
|
+
for b in content:
|
|
63
|
+
is_thinking = False
|
|
64
|
+
if isinstance(b, dict):
|
|
65
|
+
t = b.get("type")
|
|
66
|
+
if t == "thinking":
|
|
67
|
+
is_thinking = True
|
|
68
|
+
elif "thinking" in b and isinstance(b["thinking"], str):
|
|
69
|
+
is_thinking = True
|
|
70
|
+
|
|
71
|
+
if is_thinking:
|
|
72
|
+
removed_any = True
|
|
73
|
+
continue
|
|
74
|
+
filtered_output.append(b)
|
|
75
|
+
|
|
76
|
+
if removed_any:
|
|
77
|
+
ai_msg.content = filtered_output
|
|
78
|
+
messages[last_ai_index] = ai_msg
|
|
79
|
+
|
|
80
|
+
return messages
|
|
81
|
+
|
|
82
|
+
|
|
13
83
|
def add_tools(tool_config: ToolConfig, tools_to_add: ToolConfig):
|
|
14
84
|
for app_id, new_tools in tools_to_add.items():
|
|
15
85
|
all_tools = tool_config.get(app_id, []) + new_tools
|
|
@@ -333,31 +403,45 @@ def inject_context(
|
|
|
333
403
|
return namespace
|
|
334
404
|
|
|
335
405
|
|
|
336
|
-
def schema_to_signature(schema: dict, func_name="my_function") -> str:
|
|
406
|
+
def schema_to_signature(schema: dict, func_name: str = "my_function") -> str:
|
|
407
|
+
"""
|
|
408
|
+
Convert a JSON schema into a Python-style function signature string.
|
|
409
|
+
Handles fields with `type`, `anyOf`, defaults, and missing metadata safely.
|
|
410
|
+
"""
|
|
337
411
|
type_map = {
|
|
338
412
|
"integer": "int",
|
|
339
413
|
"string": "str",
|
|
340
414
|
"boolean": "bool",
|
|
341
415
|
"null": "None",
|
|
416
|
+
"number": "float",
|
|
417
|
+
"array": "list",
|
|
418
|
+
"object": "dict",
|
|
342
419
|
}
|
|
343
420
|
|
|
344
421
|
params = []
|
|
345
422
|
for name, meta in schema.items():
|
|
346
|
-
|
|
347
|
-
|
|
423
|
+
if not isinstance(meta, dict):
|
|
424
|
+
typ = "Any"
|
|
425
|
+
elif "type" in meta:
|
|
348
426
|
typ = type_map.get(meta["type"], "Any")
|
|
349
427
|
elif "anyOf" in meta:
|
|
350
|
-
types = [
|
|
351
|
-
|
|
428
|
+
types = []
|
|
429
|
+
for t in meta["anyOf"]:
|
|
430
|
+
if not isinstance(t, dict):
|
|
431
|
+
continue
|
|
432
|
+
t_type = t.get("type")
|
|
433
|
+
types.append(type_map.get(t_type, "Any") if t_type else "Any")
|
|
434
|
+
typ = " | ".join(sorted(set(types))) if types else "Any"
|
|
352
435
|
else:
|
|
353
436
|
typ = "Any"
|
|
354
437
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
438
|
+
# Handle defaults gracefully
|
|
439
|
+
default = meta.get("default")
|
|
440
|
+
if default is None:
|
|
441
|
+
params.append(f"{name}: {typ}")
|
|
442
|
+
else:
|
|
443
|
+
params.append(f"{name}: {typ} = {repr(default)}")
|
|
359
444
|
|
|
360
|
-
# join into signature
|
|
361
445
|
param_str = ",\n ".join(params)
|
|
362
446
|
return f"def {func_name}(\n {param_str},\n):"
|
|
363
447
|
|
|
@@ -394,3 +478,27 @@ def smart_truncate(
|
|
|
394
478
|
truncated = truncated[:summary_threshold] + "\n... [output truncated to fit context] ..."
|
|
395
479
|
|
|
396
480
|
return truncated
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
async def get_connected_apps_string(registry) -> str:
|
|
484
|
+
"""Get a formatted string of connected applications from the registry."""
|
|
485
|
+
if not registry:
|
|
486
|
+
return ""
|
|
487
|
+
|
|
488
|
+
try:
|
|
489
|
+
# Get connected apps from registry
|
|
490
|
+
connections = await registry.list_connected_apps()
|
|
491
|
+
if not connections:
|
|
492
|
+
return "No applications are currently connected."
|
|
493
|
+
|
|
494
|
+
# Extract app names from connections
|
|
495
|
+
connected_app_ids = {connection["app_id"] for connection in connections}
|
|
496
|
+
|
|
497
|
+
# Format the apps list
|
|
498
|
+
apps_list = []
|
|
499
|
+
for app_id in connected_app_ids:
|
|
500
|
+
apps_list.append(f"- {app_id}")
|
|
501
|
+
|
|
502
|
+
return "\n".join(apps_list)
|
|
503
|
+
except Exception:
|
|
504
|
+
return "Unable to retrieve connected applications."
|
universal_mcp/agents/llm.py
CHANGED
|
@@ -4,26 +4,33 @@ 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)
|
|
10
11
|
def load_chat_model(
|
|
11
|
-
fully_specified_name: str, temperature: float = 1.0, tags:
|
|
12
|
+
fully_specified_name: str, temperature: float = 1.0, tags: tuple[str, ...] | None = None, thinking: bool = True, disable_streaming: bool = False
|
|
12
13
|
) -> BaseChatModel:
|
|
13
14
|
"""Load a chat model from a fully specified name.
|
|
14
15
|
Args:
|
|
15
16
|
fully_specified_name (str): String in the format 'provider/model'.
|
|
16
17
|
"""
|
|
17
18
|
fully_specified_name = fully_specified_name.replace("/", ":")
|
|
19
|
+
if tags:
|
|
20
|
+
if isinstance(tags, str):
|
|
21
|
+
tags = [tags]
|
|
22
|
+
else:
|
|
23
|
+
tags = list[str](tags)
|
|
18
24
|
provider, model = fully_specified_name.split(":", maxsplit=1)
|
|
19
25
|
if provider == "anthropic":
|
|
20
26
|
return ChatAnthropic(
|
|
21
27
|
model=model,
|
|
22
28
|
temperature=temperature,
|
|
23
29
|
thinking={"type": "enabled", "budget_tokens": 2048} if thinking else None,
|
|
24
|
-
max_tokens=
|
|
30
|
+
max_tokens=8096,
|
|
25
31
|
tags=tags,
|
|
26
32
|
stream_usage=True,
|
|
33
|
+
disable_streaming = disable_streaming
|
|
27
34
|
) # pyright: ignore[reportCallIssue]
|
|
28
35
|
elif provider == "azure":
|
|
29
36
|
return AzureChatOpenAI(
|
|
@@ -33,6 +40,7 @@ def load_chat_model(
|
|
|
33
40
|
temperature=temperature,
|
|
34
41
|
tags=tags,
|
|
35
42
|
stream_usage=True,
|
|
43
|
+
disable_streaming = disable_streaming
|
|
36
44
|
)
|
|
37
45
|
elif provider == "gemini":
|
|
38
46
|
return ChatGoogleGenerativeAI(model=model, temperature=temperature)
|
|
@@ -41,8 +49,6 @@ def load_chat_model(
|
|
|
41
49
|
|
|
42
50
|
|
|
43
51
|
if __name__ == "__main__":
|
|
44
|
-
from loguru import logger
|
|
45
|
-
|
|
46
52
|
models_to_test = [
|
|
47
53
|
"azure/gpt-5-chat",
|
|
48
54
|
"anthropic/claude-4-sonnet-20250514",
|
universal_mcp/agents/react.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
from langchain.agents import create_agent
|
|
1
2
|
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
2
|
-
from langgraph.prebuilt import create_react_agent
|
|
3
3
|
from loguru import logger
|
|
4
4
|
from rich import print
|
|
5
5
|
from universal_mcp.agentr.registry import AgentrRegistry
|
|
@@ -75,10 +75,10 @@ class ReactAgent(BaseAgent):
|
|
|
75
75
|
tools = []
|
|
76
76
|
|
|
77
77
|
logger.debug(f"Initialized ReactAgent: name={self.name}, model={self.model}")
|
|
78
|
-
return
|
|
78
|
+
return create_agent(
|
|
79
79
|
self.llm,
|
|
80
80
|
tools,
|
|
81
|
-
|
|
81
|
+
system_prompt=self._build_system_message(),
|
|
82
82
|
checkpointer=self.memory,
|
|
83
83
|
)
|
|
84
84
|
|
universal_mcp/agents/sandbox.py
CHANGED
|
@@ -1,90 +1,145 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import base64
|
|
1
3
|
import contextlib
|
|
2
|
-
import inspect
|
|
3
4
|
import io
|
|
4
|
-
import
|
|
5
|
-
import re
|
|
6
|
-
import socket
|
|
7
|
-
import threading
|
|
8
|
-
import types
|
|
9
|
-
from typing import Any
|
|
5
|
+
import traceback
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
import cloudpickle as pickle
|
|
8
|
+
from loguru import logger
|
|
12
9
|
|
|
13
10
|
|
|
14
11
|
class Sandbox:
|
|
15
12
|
"""
|
|
16
|
-
A
|
|
13
|
+
A simulated environment for executing Python code cells with context
|
|
14
|
+
maintained across multiple runs.
|
|
17
15
|
"""
|
|
18
16
|
|
|
19
|
-
def __init__(self
|
|
17
|
+
def __init__(self):
|
|
18
|
+
# Dictionary to store variables (context) across runs
|
|
19
|
+
self.context = {}
|
|
20
|
+
|
|
21
|
+
def add_context(self, context: dict[str, any]):
|
|
22
|
+
"""
|
|
23
|
+
Adds a dictionary of context to the sandbox.
|
|
24
|
+
"""
|
|
25
|
+
self.context.update(context)
|
|
26
|
+
|
|
27
|
+
def save_context(self) -> str:
|
|
28
|
+
"""
|
|
29
|
+
Saves the context to a base64 string.
|
|
30
|
+
files, IO, threads, etc. are not pickable. So we only pickle the context that is pickable.
|
|
31
|
+
"""
|
|
32
|
+
pickable_context = {}
|
|
33
|
+
for key, value in self.context.items():
|
|
34
|
+
try:
|
|
35
|
+
pickle.dumps(value)
|
|
36
|
+
pickable_context[key] = value
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logger.error(f"Error picking {key}: {e}")
|
|
39
|
+
pickled_data = pickle.dumps(pickable_context)
|
|
40
|
+
base64_encoded = base64.b64encode(pickled_data).decode("utf-8")
|
|
41
|
+
return base64_encoded
|
|
42
|
+
|
|
43
|
+
def load_context(self, context: str, add_context: list[str] = []):
|
|
20
44
|
"""
|
|
21
|
-
|
|
45
|
+
Loads the context from a base64 string.
|
|
46
|
+
Also executes the add_context code strings to add to the context.
|
|
47
|
+
"""
|
|
48
|
+
if context:
|
|
49
|
+
pickled_data = base64.b64decode(context)
|
|
50
|
+
new_context = pickle.loads(pickled_data)
|
|
51
|
+
self.context.update(new_context)
|
|
52
|
+
for code in add_context:
|
|
53
|
+
self.run(code)
|
|
54
|
+
return self.context
|
|
55
|
+
|
|
56
|
+
def _filter_context(self, context: dict[str, any]) -> dict[str, any]:
|
|
57
|
+
"""
|
|
58
|
+
Filters the context to only include pickable variables.
|
|
59
|
+
"""
|
|
60
|
+
return {k: v for k, v in context.items() if not k.startswith("__")}
|
|
61
|
+
|
|
62
|
+
def run(self, code: str) -> dict[str, any]:
|
|
63
|
+
"""
|
|
64
|
+
Executes the provided Python code string in the maintained context.
|
|
65
|
+
|
|
22
66
|
Args:
|
|
23
|
-
|
|
67
|
+
code (str): The Python code to execute.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
dict: A dictionary containing the execution results.
|
|
24
71
|
"""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
72
|
+
# Prepare the execution environment:
|
|
73
|
+
# Use a copy of the context for execution locals/globals
|
|
74
|
+
exec_scope = self.context.copy()
|
|
75
|
+
|
|
76
|
+
stdout_capture = io.StringIO()
|
|
77
|
+
stderr_output = ""
|
|
28
78
|
|
|
29
|
-
|
|
79
|
+
# Use a true context manager for robust stdout capture
|
|
80
|
+
try:
|
|
81
|
+
with contextlib.redirect_stdout(stdout_capture):
|
|
82
|
+
# Execute the code. Using the same dictionary for globals and locals
|
|
83
|
+
# allows newly created variables to be visible immediately.
|
|
84
|
+
exec(code, exec_scope, exec_scope)
|
|
85
|
+
|
|
86
|
+
# Update the context with any new/modified variables
|
|
87
|
+
# Filter out dunder methods/system keys that might be introduced by exec
|
|
88
|
+
new_context = self._filter_context(exec_scope)
|
|
89
|
+
self.context.update(new_context)
|
|
90
|
+
|
|
91
|
+
except Exception:
|
|
92
|
+
# Capture the traceback for better error reporting (simulated stderr)
|
|
93
|
+
stderr_output = traceback.format_exc()
|
|
94
|
+
|
|
95
|
+
# The execution scope might contain partially defined variables,
|
|
96
|
+
# but we continue to maintain the *previous* valid context.
|
|
97
|
+
# We don't update self.context on failure to avoid polluting it.
|
|
98
|
+
|
|
99
|
+
return {"stdout": stdout_capture.getvalue(), "stderr": stderr_output, "success": stderr_output == ""}
|
|
100
|
+
|
|
101
|
+
def get_context(self) -> dict[str, any]:
|
|
30
102
|
"""
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
103
|
+
Returns a copy of the current execution context.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
dict: A copy of the context dictionary.
|
|
35
107
|
"""
|
|
108
|
+
return self.context.copy()
|
|
36
109
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
threading.Event,
|
|
43
|
-
threading.Condition,
|
|
44
|
-
threading.Semaphore,
|
|
45
|
-
queue.Queue,
|
|
46
|
-
socket.socket,
|
|
47
|
-
io.IOBase,
|
|
48
|
-
)
|
|
110
|
+
def reset(self):
|
|
111
|
+
"""
|
|
112
|
+
Resets the sandbox's context, clearing all defined variables.
|
|
113
|
+
"""
|
|
114
|
+
self.context = {}
|
|
49
115
|
|
|
50
|
-
|
|
116
|
+
async def arun(self, code: str) -> dict[str, any]:
|
|
117
|
+
"""
|
|
118
|
+
Asynchronously executes Python code, supporting top-level await.
|
|
119
|
+
"""
|
|
120
|
+
# Use a copy of the context for execution
|
|
121
|
+
exec_scope = self.context.copy()
|
|
122
|
+
stdout_capture = io.StringIO()
|
|
123
|
+
stderr_output = ""
|
|
51
124
|
|
|
52
|
-
def target():
|
|
53
|
-
try:
|
|
54
|
-
with contextlib.redirect_stdout(io.StringIO()) as f:
|
|
55
|
-
exec(code, self._locals, self._locals)
|
|
56
|
-
result_container["output"] = f.getvalue() or "<code ran, no output printed to stdout>"
|
|
57
|
-
except Exception as e:
|
|
58
|
-
result_container["output"] = "Error during execution: " + str(e)
|
|
59
|
-
|
|
60
|
-
thread = threading.Thread(target=target)
|
|
61
|
-
thread.start()
|
|
62
|
-
thread.join(self.timeout)
|
|
63
|
-
|
|
64
|
-
if thread.is_alive():
|
|
65
|
-
result_container["output"] = f"Code timeout: code execution exceeded {self.timeout} seconds."
|
|
66
|
-
|
|
67
|
-
# Filter locals for picklable/storable variables
|
|
68
|
-
all_vars = {}
|
|
69
|
-
for key, value in self._locals.items():
|
|
70
|
-
if key == "__builtins__":
|
|
71
|
-
continue
|
|
72
|
-
if inspect.iscoroutine(value) or inspect.iscoroutinefunction(value):
|
|
73
|
-
continue
|
|
74
|
-
if inspect.isasyncgen(value) or inspect.isasyncgenfunction(value):
|
|
75
|
-
continue
|
|
76
|
-
if isinstance(value, EXCLUDE_TYPES):
|
|
77
|
-
continue
|
|
78
|
-
if not callable(value) or not hasattr(value, "__name__"):
|
|
79
|
-
all_vars[key] = value
|
|
80
|
-
|
|
81
|
-
self._locals = all_vars
|
|
82
|
-
|
|
83
|
-
# Safely derive context
|
|
84
125
|
try:
|
|
85
|
-
|
|
126
|
+
# Compile the code with the special flag to allow top-level await
|
|
127
|
+
compiled_code = compile(code, "<string>", "exec", flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
|
|
128
|
+
|
|
129
|
+
with contextlib.redirect_stdout(stdout_capture):
|
|
130
|
+
# Eval the compiled code to get a coroutine
|
|
131
|
+
coroutine = eval(compiled_code, exec_scope, exec_scope)
|
|
132
|
+
|
|
133
|
+
# Await the coroutine to run the code if it's async
|
|
134
|
+
if coroutine:
|
|
135
|
+
await coroutine
|
|
136
|
+
|
|
137
|
+
# Update the context with any new/modified variables
|
|
138
|
+
new_context = self._filter_context(exec_scope)
|
|
139
|
+
if new_context:
|
|
140
|
+
self.context.update(new_context)
|
|
141
|
+
|
|
86
142
|
except Exception:
|
|
87
|
-
|
|
88
|
-
pass
|
|
143
|
+
stderr_output = traceback.format_exc()
|
|
89
144
|
|
|
90
|
-
return
|
|
145
|
+
return {"stdout": stdout_capture.getvalue(), "stderr": stderr_output, "success": stderr_output == ""}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from typing import Any, Literal, cast
|
|
3
3
|
|
|
4
|
-
from langchain.
|
|
4
|
+
from langchain.agents import create_agent
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
from universal_mcp.applications.application import BaseApplication
|
|
7
7
|
|
|
@@ -91,8 +91,8 @@ class LlmApp(BaseApplication):
|
|
|
91
91
|
|
|
92
92
|
full_prompt = f"{prompt}\n\nContext:\n{context_str}\n\n"
|
|
93
93
|
|
|
94
|
-
model = load_chat_model("azure/gpt-5-mini")
|
|
95
|
-
response = model.with_retry(stop_after_attempt=MAX_RETRIES).invoke(full_prompt)
|
|
94
|
+
model = load_chat_model("azure/gpt-5-mini", disable_streaming = True, tags=("quiet",))
|
|
95
|
+
response = model.with_retry(stop_after_attempt=MAX_RETRIES).invoke(full_prompt, stream=False)
|
|
96
96
|
return str(response.content)
|
|
97
97
|
|
|
98
98
|
def classify_data(
|
|
@@ -151,22 +151,22 @@ class LlmApp(BaseApplication):
|
|
|
151
151
|
f"This is a classification task.\nPossible classes and descriptions:\n"
|
|
152
152
|
f"{json.dumps(class_descriptions, indent=2)}\n\n"
|
|
153
153
|
f"Context:\n{context_str}\n\n"
|
|
154
|
-
"Return ONLY a valid JSON object, no extra text."
|
|
155
154
|
)
|
|
156
155
|
|
|
157
|
-
model = init_chat_model(model="claude-4-sonnet-20250514", temperature=0)
|
|
158
|
-
|
|
159
156
|
class ClassificationResult(BaseModel):
|
|
160
157
|
probabilities: dict[str, float] = Field(..., description="The probabilities for each class.")
|
|
161
158
|
reason: str = Field(..., description="The reasoning behind the classification.")
|
|
162
159
|
top_class: str = Field(..., description="The class with the highest probability.")
|
|
163
160
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
model = load_chat_model("azure/gpt-5-mini", temperature=0, disable_streaming = True, tags=("quiet",))
|
|
162
|
+
agent = create_agent(
|
|
163
|
+
model=model,
|
|
164
|
+
tools=[],
|
|
165
|
+
response_format=ClassificationResult, # Auto-selects ProviderStrategy
|
|
168
166
|
)
|
|
169
|
-
|
|
167
|
+
|
|
168
|
+
result = agent.invoke({"messages": [{"role": "user", "content": prompt}]}, stream=False)
|
|
169
|
+
return result["structured_response"].model_dump()
|
|
170
170
|
|
|
171
171
|
def extract_data(
|
|
172
172
|
self,
|
|
@@ -229,12 +229,12 @@ class LlmApp(BaseApplication):
|
|
|
229
229
|
"Return ONLY a valid JSON object that conforms to the provided schema, with no extra text."
|
|
230
230
|
)
|
|
231
231
|
|
|
232
|
-
model =
|
|
232
|
+
model = load_chat_model("azure/gpt-5-mini", temperature=0, disable_streaming = True, tags=("quiet",))
|
|
233
233
|
|
|
234
234
|
response = (
|
|
235
235
|
model.with_structured_output(schema=output_schema, method="json_mode")
|
|
236
236
|
.with_retry(stop_after_attempt=MAX_RETRIES)
|
|
237
|
-
.invoke(prompt)
|
|
237
|
+
.invoke(prompt, stream=False)
|
|
238
238
|
)
|
|
239
239
|
return cast(dict[str, Any], response)
|
|
240
240
|
|
|
@@ -282,14 +282,15 @@ class LlmApp(BaseApplication):
|
|
|
282
282
|
|
|
283
283
|
prompt = f"{task_instructions}\n\nContext:\n{context_str}\n\nReturn ONLY a valid JSON object, no extra text."
|
|
284
284
|
|
|
285
|
-
model =
|
|
285
|
+
model = load_chat_model("azure/gpt-5-mini", temperature=0, disable_streaming = True, tags=("quiet",))
|
|
286
286
|
|
|
287
|
-
|
|
288
|
-
model
|
|
289
|
-
|
|
290
|
-
|
|
287
|
+
agent = create_agent(
|
|
288
|
+
model=model,
|
|
289
|
+
tools=[],
|
|
290
|
+
response_format=output_schema,
|
|
291
291
|
)
|
|
292
|
-
|
|
292
|
+
result = agent.invoke({"messages": [{"role": "user", "content": prompt}]}, stream=False)
|
|
293
|
+
return result["structured_response"]
|
|
293
294
|
|
|
294
295
|
def list_tools(self):
|
|
295
296
|
return [
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: universal-mcp-agents
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.23
|
|
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
|
|
7
7
|
Author-email: Manoj Bajaj <manojbajaj95@gmail.com>
|
|
8
8
|
License: MIT
|
|
9
9
|
Requires-Python: >=3.11
|
|
10
|
+
Requires-Dist: cloudpickle>=3.1.1
|
|
10
11
|
Requires-Dist: langchain-anthropic>=0.3.19
|
|
11
12
|
Requires-Dist: langchain-google-genai>=2.1.10
|
|
12
13
|
Requires-Dist: langchain-openai>=0.3.32
|
|
13
14
|
Requires-Dist: langgraph>=0.6.6
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist: universal-mcp
|
|
16
|
-
Requires-Dist: universal-mcp>=0.1.24rc25
|
|
15
|
+
Requires-Dist: universal-mcp-applications>=0.1.30
|
|
16
|
+
Requires-Dist: universal-mcp>=0.1.24rc29
|
|
17
17
|
Provides-Extra: dev
|
|
18
18
|
Requires-Dist: pre-commit; extra == 'dev'
|
|
19
19
|
Requires-Dist: ruff; extra == 'dev'
|
|
20
|
+
Requires-Dist: typer>=0.17.4; extra == 'dev'
|
|
20
21
|
Provides-Extra: test
|
|
21
|
-
Requires-Dist: pytest-asyncio>=1.
|
|
22
|
+
Requires-Dist: pytest-asyncio>=1.2.0; extra == 'test'
|
|
22
23
|
Requires-Dist: pytest-cov; extra == 'test'
|
|
23
24
|
Requires-Dist: pytest<9.0.0,>=7.0.0; extra == 'test'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
universal_mcp/agents/__init__.py,sha256=
|
|
2
|
-
universal_mcp/agents/base.py,sha256=
|
|
3
|
-
universal_mcp/agents/cli.py,sha256=
|
|
1
|
+
universal_mcp/agents/__init__.py,sha256=Ythw8tyq7p-w1SPnuO2JtS4TvYEP75PkQpdyvZv-ww4,914
|
|
2
|
+
universal_mcp/agents/base.py,sha256=HkTQzh8EudkG4_5teiD0aDidWpfp2obOz0XR9XsZeUI,7949
|
|
3
|
+
universal_mcp/agents/cli.py,sha256=d3O5BxkT4axHcKnL7gM1SvneWoZMh1QQoElk03KeuU4,950
|
|
4
4
|
universal_mcp/agents/hil.py,sha256=_5PCK6q0goGm8qylJq44aSp2MadP-yCPvhOJYKqWLMo,3808
|
|
5
|
-
universal_mcp/agents/llm.py,sha256=
|
|
6
|
-
universal_mcp/agents/react.py,sha256=
|
|
7
|
-
universal_mcp/agents/sandbox.py,sha256=
|
|
5
|
+
universal_mcp/agents/llm.py,sha256=AOijT3hCt3VId97FnvARsPG9lx0sLmn-yiUo8Qx90lQ,2043
|
|
6
|
+
universal_mcp/agents/react.py,sha256=ocYm94HOiJVI2zwTjO1K2PNfVY7EILLJ6cd__jnGHPs,3327
|
|
7
|
+
universal_mcp/agents/sandbox.py,sha256=YxTGp_zsajuN7FUn0Q4PFjuXczgLht7oKql_gyb2Gf4,5112
|
|
8
8
|
universal_mcp/agents/simple.py,sha256=NSATg5TWzsRNS7V3LFiDG28WSOCIwCdcC1g7NRwg2nM,2095
|
|
9
9
|
universal_mcp/agents/utils.py,sha256=P6W9k6XAOBp6tdjC2VTP4tE0B2M4-b1EDmr-ylJ47Pw,7765
|
|
10
10
|
universal_mcp/agents/bigtool/__init__.py,sha256=mZG8dsaCVyKlm82otxtiTA225GIFLUCUUYPEIPF24uw,2299
|
|
@@ -13,7 +13,7 @@ universal_mcp/agents/bigtool/agent.py,sha256=mtCDNN8WjE2hjJjooDqusmbferKBHeJMHrh
|
|
|
13
13
|
universal_mcp/agents/bigtool/context.py,sha256=ny7gd-vvVpUOYAeQbAEUT0A6Vm6Nn2qGywxTzPBzYFg,929
|
|
14
14
|
universal_mcp/agents/bigtool/graph.py,sha256=2Sy0dtevTWeT3hJDq4BDerZFvk_zJqx15j8VH2XLq8Y,5848
|
|
15
15
|
universal_mcp/agents/bigtool/prompts.py,sha256=Joi5mCzZX63aM_6eBrMOKuNRHjTkceVIibSsGBGqhYE,2041
|
|
16
|
-
universal_mcp/agents/bigtool/state.py,sha256=
|
|
16
|
+
universal_mcp/agents/bigtool/state.py,sha256=Voh7HXGC0PVe_0qoRZ8ZYg9akg65_2jQIAV2eIwperE,737
|
|
17
17
|
universal_mcp/agents/bigtool/tools.py,sha256=-u80ta6xEaqzEMSzDVe3QZiTZm3YlgLkBD8WTghzClw,6315
|
|
18
18
|
universal_mcp/agents/builder/__main__.py,sha256=VJDJOr-dJJerT53ibh5LVqIsMJ0m0sG2UlzFB784pKw,11680
|
|
19
19
|
universal_mcp/agents/builder/builder.py,sha256=mh3MZpMVB1FE1DWzvMW9NnfiaF145VGn8cJzKSYUlzY,8587
|
|
@@ -21,24 +21,24 @@ universal_mcp/agents/builder/helper.py,sha256=8igR1b3Gy_N2u3WxHYKIWzvw7F5BMnfpO2
|
|
|
21
21
|
universal_mcp/agents/builder/prompts.py,sha256=8Xs6uzTUHguDRngVMLak3lkXFkk2VV_uQXaDllzP5cI,4670
|
|
22
22
|
universal_mcp/agents/builder/state.py,sha256=7DeWllxfN-yD6cd9wJ3KIgjO8TctkJvVjAbZT8W_zqk,922
|
|
23
23
|
universal_mcp/agents/codeact0/__init__.py,sha256=8-fvUo1Sm6dURGI-lW-X3Kd78LqySYbb5NMkNJ4NDwg,76
|
|
24
|
-
universal_mcp/agents/codeact0/__main__.py,sha256=
|
|
25
|
-
universal_mcp/agents/codeact0/agent.py,sha256=
|
|
24
|
+
universal_mcp/agents/codeact0/__main__.py,sha256=YyIoecUcKVUhTcCACzLlSmYrayMDsdwzDEqaV4VV4CE,766
|
|
25
|
+
universal_mcp/agents/codeact0/agent.py,sha256=sJmTrFudHMJkkRVJgmd3KdB-kFU-yCwPvyRJE18pJ3g,23497
|
|
26
26
|
universal_mcp/agents/codeact0/config.py,sha256=H-1woj_nhSDwf15F63WYn723y4qlRefXzGxuH81uYF0,2215
|
|
27
27
|
universal_mcp/agents/codeact0/langgraph_agent.py,sha256=8nz2wq-LexImx-l1y9_f81fK72IQetnCeljwgnduNGY,420
|
|
28
28
|
universal_mcp/agents/codeact0/llm_tool.py,sha256=-pAz04OrbZ_dJ2ueysT1qZd02DrbLY4EbU0tiuF_UNU,798
|
|
29
|
-
universal_mcp/agents/codeact0/prompts.py,sha256=
|
|
30
|
-
universal_mcp/agents/codeact0/sandbox.py,sha256=
|
|
31
|
-
universal_mcp/agents/codeact0/state.py,sha256=
|
|
32
|
-
universal_mcp/agents/codeact0/tools.py,sha256=
|
|
33
|
-
universal_mcp/agents/codeact0/utils.py,sha256=
|
|
29
|
+
universal_mcp/agents/codeact0/prompts.py,sha256=TaGxbEka0wxaHIynVe_El_8Qy7bhIpXFLW817lGq3os,17264
|
|
30
|
+
universal_mcp/agents/codeact0/sandbox.py,sha256=Zcr7fvYtcGbwNWd7RPV7-Btl2HtycPIPofEGVmzxSmE,4696
|
|
31
|
+
universal_mcp/agents/codeact0/state.py,sha256=cf-94hfVub-HSQJk6b7_SzqBS-oxMABjFa8jqyjdDK0,1925
|
|
32
|
+
universal_mcp/agents/codeact0/tools.py,sha256=7w-04moo7gBkqjmGzz_rTbmJEG23W0vd71pZ0D9CA9M,23299
|
|
33
|
+
universal_mcp/agents/codeact0/utils.py,sha256=eOdqemLx6RqI5g--vtM9WP2LHrYXro2bxScNAVUU0Do,19683
|
|
34
34
|
universal_mcp/agents/shared/__main__.py,sha256=XxH5qGDpgFWfq7fwQfgKULXGiUgeTp_YKfcxftuVZq8,1452
|
|
35
35
|
universal_mcp/agents/shared/prompts.py,sha256=yjP3zbbuKi87qCj21qwTTicz8TqtkKgnyGSeEjMu3ho,3761
|
|
36
36
|
universal_mcp/agents/shared/tool_node.py,sha256=DC9F-Ri28Pam0u3sXWNODVgmj9PtAEUb5qP1qOoGgfs,9169
|
|
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=
|
|
40
|
+
universal_mcp/applications/llm/app.py,sha256=2BIH6JDqw6LPD9Iuo3_LoXa48813o6G4gmeRBR_v9oc,12933
|
|
41
41
|
universal_mcp/applications/ui/app.py,sha256=c7OkZsO2fRtndgAzAQbKu-1xXRuRp9Kjgml57YD2NR4,9459
|
|
42
|
-
universal_mcp_agents-0.1.
|
|
43
|
-
universal_mcp_agents-0.1.
|
|
44
|
-
universal_mcp_agents-0.1.
|
|
42
|
+
universal_mcp_agents-0.1.23.dist-info/METADATA,sha256=sBzwtuZHot_QhhiMDcIWVVHxi1V373IFHpBTYl9zFyg,928
|
|
43
|
+
universal_mcp_agents-0.1.23.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
44
|
+
universal_mcp_agents-0.1.23.dist-info/RECORD,,
|
|
File without changes
|