universal-mcp-agents 0.1.3__tar.gz → 0.1.4__tar.gz

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.

Files changed (66) hide show
  1. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/PKG-INFO +2 -2
  2. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/pyproject.toml +2 -2
  3. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/evals/evaluators.py +6 -2
  4. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/evals/run.py +21 -6
  5. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/evals/utils.py +45 -13
  6. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/tests/test_agents.py +107 -24
  7. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/autoagent/graph.py +30 -11
  8. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/autoagent/studio.py +1 -7
  9. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/base.py +55 -9
  10. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool/__init__.py +3 -1
  11. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool/graph.py +78 -25
  12. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool2/__init__.py +3 -1
  13. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool2/agent.py +2 -1
  14. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool2/context.py +0 -1
  15. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool2/graph.py +76 -32
  16. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtoolcache/__init__.py +6 -2
  17. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtoolcache/agent.py +2 -1
  18. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtoolcache/context.py +0 -1
  19. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtoolcache/graph.py +88 -59
  20. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtoolcache/prompts.py +29 -0
  21. universal_mcp_agents-0.1.4/src/universal_mcp/agents/bigtoolcache/tools_all.txt +956 -0
  22. universal_mcp_agents-0.1.4/src/universal_mcp/agents/bigtoolcache/tools_important.txt +474 -0
  23. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/builder.py +19 -5
  24. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/codeact/__init__.py +16 -4
  25. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/hil.py +16 -4
  26. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/llm.py +5 -1
  27. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/planner/__init__.py +7 -3
  28. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/planner/__main__.py +3 -1
  29. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/planner/graph.py +3 -1
  30. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/react.py +5 -1
  31. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/shared/tool_node.py +24 -8
  32. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/simple.py +8 -1
  33. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/tools.py +9 -3
  34. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/utils.py +35 -7
  35. universal_mcp_agents-0.1.4/tool_retrieve.py +47 -0
  36. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/uv.lock +24 -5
  37. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/.gitignore +0 -0
  38. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/README.md +0 -0
  39. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/bump_and_release.sh +0 -0
  40. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/evals/__init__.py +0 -0
  41. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/evals/dataset.py +0 -0
  42. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/evals/datasets/exact.jsonl +0 -0
  43. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/evals/datasets/tasks.jsonl +0 -0
  44. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/evals/test.py +0 -0
  45. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/__init__.py +0 -0
  46. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/autoagent/__init__.py +0 -0
  47. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/autoagent/__main__.py +0 -0
  48. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/autoagent/context.py +0 -0
  49. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/autoagent/prompts.py +0 -0
  50. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/autoagent/state.py +0 -0
  51. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/autoagent/utils.py +0 -0
  52. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool/__main__.py +0 -0
  53. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool/context.py +0 -0
  54. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool/prompts.py +0 -0
  55. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool/state.py +0 -0
  56. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool2/__main__.py +0 -0
  57. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool2/prompts.py +0 -0
  58. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtool2/state.py +0 -0
  59. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtoolcache/__main__.py +0 -0
  60. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/bigtoolcache/state.py +0 -0
  61. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/cli.py +0 -0
  62. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/codeact/sandbox.py +0 -0
  63. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/codeact/test.py +0 -0
  64. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/codeact/utils.py +0 -0
  65. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/planner/prompts.py +0 -0
  66. {universal_mcp_agents-0.1.3 → universal_mcp_agents-0.1.4}/src/universal_mcp/agents/planner/state.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp-agents
3
- Version: 0.1.3
3
+ Version: 0.1.4
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
@@ -11,7 +11,7 @@ Requires-Dist: langchain-anthropic>=0.3.19
11
11
  Requires-Dist: langchain-google-genai>=2.1.10
12
12
  Requires-Dist: langchain-openai>=0.3.32
13
13
  Requires-Dist: langgraph>=0.6.6
14
- Requires-Dist: universal-mcp-applications>=0.1.2
14
+ Requires-Dist: universal-mcp-applications>=0.1.4
15
15
  Requires-Dist: universal-mcp>=0.1.24rc17
16
16
  Provides-Extra: dev
17
17
  Requires-Dist: pre-commit; extra == 'dev'
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "universal-mcp-agents"
9
- version = "0.1.3"
9
+ version = "0.1.4"
10
10
  description = "Add your description here"
11
11
  readme = "README.md"
12
12
  authors = [
@@ -19,7 +19,7 @@ dependencies = [
19
19
  "langchain-openai>=0.3.32",
20
20
  "langgraph>=0.6.6",
21
21
  "universal-mcp >= 0.1.24rc17",
22
- "universal-mcp-applications>=0.1.2",
22
+ "universal-mcp-applications>=0.1.4",
23
23
  ]
24
24
 
25
25
  [project.license]
@@ -11,13 +11,17 @@ def exact_match_evaluator(run: Run, example: Example | None = None) -> Evaluatio
11
11
  and the expected output from the dataset.
12
12
  """
13
13
  if example is None or "expected_output" not in example.outputs:
14
- return EvaluationResult(key="exact_match", score=0, comment="No expected output provided.")
14
+ return EvaluationResult(
15
+ key="exact_match", score=0, comment="No expected output provided."
16
+ )
15
17
 
16
18
  # The agent's response might be in a list of messages
17
19
  agent_response_raw = run.outputs.get("output", "")
18
20
  if isinstance(agent_response_raw, list):
19
21
  # Extract text from the last dictionary in the list
20
- agent_response = agent_response_raw[-1].get("text", "").strip() if agent_response_raw else ""
22
+ agent_response = (
23
+ agent_response_raw[-1].get("text", "").strip() if agent_response_raw else ""
24
+ )
21
25
  else:
22
26
  agent_response = str(agent_response_raw).strip()
23
27
 
@@ -34,7 +34,9 @@ def get_agent(agent_name: str) -> BaseAgent:
34
34
  elif agent_name == "auto":
35
35
  return AutoAgent(name="auto-agent", **common_params)
36
36
  else:
37
- raise ValueError(f"Unknown agent: {agent_name}. Available agents: simple, react, auto")
37
+ raise ValueError(
38
+ f"Unknown agent: {agent_name}. Available agents: simple, react, auto"
39
+ )
38
40
 
39
41
 
40
42
  # 2. Evaluator Registry
@@ -50,7 +52,9 @@ def get_evaluator(evaluator_name: str) -> RunEvaluator:
50
52
  """
51
53
  evaluator = EVALUATORS.get(evaluator_name)
52
54
  if evaluator is None:
53
- raise ValueError(f"Unknown evaluator: {evaluator_name}. Available evaluators: {', '.join(EVALUATORS.keys())}")
55
+ raise ValueError(
56
+ f"Unknown evaluator: {evaluator_name}. Available evaluators: {', '.join(EVALUATORS.keys())}"
57
+ )
54
58
  return evaluator
55
59
 
56
60
 
@@ -83,7 +87,9 @@ async def main(agent_name: str, dataset_path: str, evaluator_name: str):
83
87
  """
84
88
  The main function for the evaluation CLI.
85
89
  """
86
- print(f"Starting evaluation with agent='{agent_name}', dataset='{dataset_path}', evaluator='{evaluator_name}'")
90
+ print(
91
+ f"Starting evaluation with agent='{agent_name}', dataset='{dataset_path}', evaluator='{evaluator_name}'"
92
+ )
87
93
 
88
94
  # 1. Get the agent and evaluator
89
95
  agent = get_agent(agent_name)
@@ -104,12 +110,15 @@ async def main(agent_name: str, dataset_path: str, evaluator_name: str):
104
110
  # If dataset with same name and examples exists, read it.
105
111
  # Otherwise, a new one is created.
106
112
  dataset = client.create_dataset(
107
- dataset_name, description=f"Dataset for {agent_name} evaluation with {evaluator_name}."
113
+ dataset_name,
114
+ description=f"Dataset for {agent_name} evaluation with {evaluator_name}.",
108
115
  )
109
116
  for example in dataset_examples:
110
117
  client.create_example(
111
118
  inputs={"user_input": example["user_input"]},
112
- outputs={"output": example.get("expected_output")} if "expected_output" in example else None,
119
+ outputs={"output": example.get("expected_output")}
120
+ if "expected_output" in example
121
+ else None,
113
122
  dataset_id=dataset.id,
114
123
  )
115
124
  print(f"Created and populated dataset '{dataset_name}' for this run.")
@@ -146,4 +155,10 @@ if __name__ == "__main__":
146
155
  )
147
156
  args = parser.parse_args()
148
157
 
149
- asyncio.run(main(agent_name=args.agent, dataset_path=args.dataset, evaluator_name=args.evaluator))
158
+ asyncio.run(
159
+ main(
160
+ agent_name=args.agent,
161
+ dataset_path=args.dataset,
162
+ evaluator_name=args.evaluator,
163
+ )
164
+ )
@@ -8,7 +8,9 @@ from evals.dataset import load_dataset
8
8
  load_dotenv()
9
9
 
10
10
 
11
- def upload_runs_to_dataset(project_name: str, dataset_name: str, dataset_description: str = ""):
11
+ def upload_runs_to_dataset(
12
+ project_name: str, dataset_name: str, dataset_description: str = ""
13
+ ):
12
14
  """
13
15
  Uploads runs from a LangSmith project to a dataset.
14
16
 
@@ -36,7 +38,9 @@ def upload_runs_to_dataset(project_name: str, dataset_name: str, dataset_descrip
36
38
  )
37
39
  example_count += 1
38
40
 
39
- print(f"✅ Successfully uploaded {example_count} runs from project '{project_name}' to dataset '{dataset_name}'.")
41
+ print(
42
+ f"✅ Successfully uploaded {example_count} runs from project '{project_name}' to dataset '{dataset_name}'."
43
+ )
40
44
 
41
45
 
42
46
  def upload_dataset_from_file(
@@ -71,7 +75,9 @@ def upload_dataset_from_file(
71
75
  outputs = {key: example[key] for key in output_keys if key in example}
72
76
  client.create_example(inputs=inputs, outputs=outputs, dataset_id=dataset.id)
73
77
 
74
- print(f"✅ Successfully uploaded {len(examples)} examples from '{file_path}' to dataset '{dataset_name}'.")
78
+ print(
79
+ f"✅ Successfully uploaded {len(examples)} examples from '{file_path}' to dataset '{dataset_name}'."
80
+ )
75
81
 
76
82
 
77
83
  if __name__ == "__main__":
@@ -79,22 +85,48 @@ if __name__ == "__main__":
79
85
  subparsers = parser.add_subparsers(dest="command", required=True)
80
86
 
81
87
  # Sub-parser for uploading runs from a project
82
- parser_runs = subparsers.add_parser("upload-runs", help="Upload runs from a project to a dataset.")
83
- parser_runs.add_argument("--project-name", required=True, help="The LangSmith project name.")
84
- parser_runs.add_argument("--dataset-name", required=True, help="The target dataset name.")
88
+ parser_runs = subparsers.add_parser(
89
+ "upload-runs", help="Upload runs from a project to a dataset."
90
+ )
85
91
  parser_runs.add_argument(
86
- "--dataset-description", default="Dataset from project runs.", help="Description for the dataset."
92
+ "--project-name", required=True, help="The LangSmith project name."
93
+ )
94
+ parser_runs.add_argument(
95
+ "--dataset-name", required=True, help="The target dataset name."
96
+ )
97
+ parser_runs.add_argument(
98
+ "--dataset-description",
99
+ default="Dataset from project runs.",
100
+ help="Description for the dataset.",
87
101
  )
88
102
 
89
103
  # Sub-parser for uploading a dataset from a file
90
- parser_file = subparsers.add_parser("upload-file", help="Upload a dataset from a local file.")
91
- parser_file.add_argument("--file-path", required=True, help="Path to the local dataset file (CSV or JSONL).")
92
- parser_file.add_argument("--dataset-name", required=True, help="The name for the dataset in LangSmith.")
104
+ parser_file = subparsers.add_parser(
105
+ "upload-file", help="Upload a dataset from a local file."
106
+ )
107
+ parser_file.add_argument(
108
+ "--file-path",
109
+ required=True,
110
+ help="Path to the local dataset file (CSV or JSONL).",
111
+ )
112
+ parser_file.add_argument(
113
+ "--dataset-name", required=True, help="The name for the dataset in LangSmith."
114
+ )
115
+ parser_file.add_argument(
116
+ "--dataset-description",
117
+ default="Dataset uploaded from file.",
118
+ help="Description for the dataset.",
119
+ )
120
+ parser_file.add_argument(
121
+ "--input-keys",
122
+ required=True,
123
+ help="Comma-separated list of input column names.",
124
+ )
93
125
  parser_file.add_argument(
94
- "--dataset-description", default="Dataset uploaded from file.", help="Description for the dataset."
126
+ "--output-keys",
127
+ required=True,
128
+ help="Comma-separated list of output column names.",
95
129
  )
96
- parser_file.add_argument("--input-keys", required=True, help="Comma-separated list of input column names.")
97
- parser_file.add_argument("--output-keys", required=True, help="Comma-separated list of output column names.")
98
130
 
99
131
  args = parser.parse_args()
100
132
 
@@ -21,14 +21,26 @@ class MockToolRegistry(ToolRegistry):
21
21
  def __init__(self, **kwargs: Any):
22
22
  """Initialize the MockToolRegistry."""
23
23
  self._apps = [
24
- {"id": "google-mail", "name": "google-mail", "description": "Send and manage emails."},
25
- {"id": "slack", "name": "slack", "description": "Team communication and messaging."},
24
+ {
25
+ "id": "google-mail",
26
+ "name": "google-mail",
27
+ "description": "Send and manage emails.",
28
+ },
29
+ {
30
+ "id": "slack",
31
+ "name": "slack",
32
+ "description": "Team communication and messaging.",
33
+ },
26
34
  {
27
35
  "id": "google-calendar",
28
36
  "name": "google-calendar",
29
37
  "description": "Schedule and manage calendar events.",
30
38
  },
31
- {"id": "jira", "name": "jira", "description": "Project tracking and issue management."},
39
+ {
40
+ "id": "jira",
41
+ "name": "jira",
42
+ "description": "Project tracking and issue management.",
43
+ },
32
44
  {
33
45
  "id": "github",
34
46
  "name": "github",
@@ -38,23 +50,67 @@ class MockToolRegistry(ToolRegistry):
38
50
  self._connected_apps = ["google-mail", "google-calendar", "github"]
39
51
  self._tools = {
40
52
  "google-mail": [
41
- {"id": "send_email", "name": "send_email", "description": "Send an email to a recipient."},
42
- {"id": "read_email", "name": "read_email", "description": "Read emails from inbox."},
43
- {"id": "create_draft", "name": "create_draft", "description": "Create a draft email."},
53
+ {
54
+ "id": "send_email",
55
+ "name": "send_email",
56
+ "description": "Send an email to a recipient.",
57
+ },
58
+ {
59
+ "id": "read_email",
60
+ "name": "read_email",
61
+ "description": "Read emails from inbox.",
62
+ },
63
+ {
64
+ "id": "create_draft",
65
+ "name": "create_draft",
66
+ "description": "Create a draft email.",
67
+ },
44
68
  ],
45
69
  "slack": [
46
- {"id": "send_message", "name": "send_message", "description": "Send a message to a team channel."},
47
- {"id": "read_channel", "name": "read_channel", "description": "Read messages from a channel."},
70
+ {
71
+ "id": "send_message",
72
+ "name": "send_message",
73
+ "description": "Send a message to a team channel.",
74
+ },
75
+ {
76
+ "id": "read_channel",
77
+ "name": "read_channel",
78
+ "description": "Read messages from a channel.",
79
+ },
48
80
  ],
49
81
  "google-calendar": [
50
- {"id": "create_event", "name": "create_event", "description": "Create a new calendar event."},
51
- {"id": "find_event", "name": "find_event", "description": "Find an event in the calendar."},
82
+ {
83
+ "id": "create_event",
84
+ "name": "create_event",
85
+ "description": "Create a new calendar event.",
86
+ },
87
+ {
88
+ "id": "find_event",
89
+ "name": "find_event",
90
+ "description": "Find an event in the calendar.",
91
+ },
52
92
  ],
53
93
  "github": [
54
- {"id": "create_issue", "name": "create_issue", "description": "Create an issue in a repository."},
55
- {"id": "get_issue", "name": "get_issue", "description": "Get details of a specific issue."},
56
- {"id": "create_pull_request", "name": "create_pull_request", "description": "Create a pull request."},
57
- {"id": "get_repository", "name": "get_repository", "description": "Get details of a repository."},
94
+ {
95
+ "id": "create_issue",
96
+ "name": "create_issue",
97
+ "description": "Create an issue in a repository.",
98
+ },
99
+ {
100
+ "id": "get_issue",
101
+ "name": "get_issue",
102
+ "description": "Get details of a specific issue.",
103
+ },
104
+ {
105
+ "id": "create_pull_request",
106
+ "name": "create_pull_request",
107
+ "description": "Create a pull request.",
108
+ },
109
+ {
110
+ "id": "get_repository",
111
+ "name": "get_repository",
112
+ "description": "Get details of a repository.",
113
+ },
58
114
  ],
59
115
  }
60
116
  self._tool_mappings = {
@@ -94,7 +150,11 @@ class MockToolRegistry(ToolRegistry):
94
150
  ) -> list[dict[str, Any]]:
95
151
  """Search for apps by a query."""
96
152
  query = query.lower()
97
- results = [app for app in self._apps if query in app["name"].lower() or query in app["description"].lower()]
153
+ results = [
154
+ app
155
+ for app in self._apps
156
+ if query in app["name"].lower() or query in app["description"].lower()
157
+ ]
98
158
  return results[:limit]
99
159
 
100
160
  async def list_tools(
@@ -126,7 +186,12 @@ class MockToolRegistry(ToolRegistry):
126
186
  results = [tool for tool in all_app_tools if tool["name"] in found_tool_names]
127
187
 
128
188
  if not results:
129
- results = [{"name": "general_purpose_tool", "description": "A general purpose tool."}]
189
+ results = [
190
+ {
191
+ "name": "general_purpose_tool",
192
+ "description": "A general purpose tool.",
193
+ }
194
+ ]
130
195
 
131
196
  return results[:limit]
132
197
 
@@ -145,7 +210,9 @@ class MockToolRegistry(ToolRegistry):
145
210
  # Return a list of mock tools for the ReAct agent to use
146
211
  return [mock_tool_callable]
147
212
 
148
- async def call_tool(self, tool_name: str, tool_args: dict[str, Any]) -> dict[str, Any]:
213
+ async def call_tool(
214
+ self, tool_name: str, tool_args: dict[str, Any]
215
+ ) -> dict[str, Any]:
149
216
  """Call a tool with the given name and arguments."""
150
217
  print(f"MockToolRegistry: Called tool '{tool_name}' with args {tool_args}")
151
218
  return {"status": f"task has been done by tool {tool_name}"}
@@ -172,7 +239,9 @@ class TestToolFinderGraph:
172
239
  """Test Case 1: Simple case (Connected App)"""
173
240
  task = "Send an email to my manager about the project update."
174
241
  graph = build_tool_node_graph(llm, registry)
175
- final_state = await graph.ainvoke({"task": task, "messages": [HumanMessage(content=task)]})
242
+ final_state = await graph.ainvoke(
243
+ {"task": task, "messages": [HumanMessage(content=task)]}
244
+ )
176
245
  assert final_state["apps_required"] is True
177
246
  assert "google-mail" in final_state["relevant_apps"]
178
247
  assert "google-mail" in final_state["apps_with_tools"]
@@ -183,7 +252,9 @@ class TestToolFinderGraph:
183
252
  """Test Case 2: Multiple apps found"""
184
253
  task = "Send a message to my team about the new design."
185
254
  graph = build_tool_node_graph(llm, registry)
186
- final_state = await graph.ainvoke({"task": task, "messages": [HumanMessage(content=task)]})
255
+ final_state = await graph.ainvoke(
256
+ {"task": task, "messages": [HumanMessage(content=task)]}
257
+ )
187
258
  assert final_state["apps_required"] is True
188
259
  assert "google-mail" in final_state["relevant_apps"]
189
260
  assert "slack" in final_state["relevant_apps"]
@@ -195,7 +266,9 @@ class TestToolFinderGraph:
195
266
  """Test Case 3: No relevant app"""
196
267
  task = "Can you create a blog post on my wordpress site?"
197
268
  graph = build_tool_node_graph(llm, registry)
198
- final_state = await graph.ainvoke({"task": task, "messages": [HumanMessage(content=task)]})
269
+ final_state = await graph.ainvoke(
270
+ {"task": task, "messages": [HumanMessage(content=task)]}
271
+ )
199
272
  assert final_state["apps_required"] is True
200
273
  assert not final_state["relevant_apps"]
201
274
  assert not final_state["apps_with_tools"]
@@ -205,7 +278,9 @@ class TestToolFinderGraph:
205
278
  """Test Case 4: Multiple tools in one app"""
206
279
  task = "Create a new issue for a bug in our github repository, and send message on slack about the issue."
207
280
  graph = build_tool_node_graph(llm, registry)
208
- final_state = await graph.ainvoke({"task": task, "messages": [HumanMessage(content=task)]})
281
+ final_state = await graph.ainvoke(
282
+ {"task": task, "messages": [HumanMessage(content=task)]}
283
+ )
209
284
  assert final_state["apps_required"] is True
210
285
  assert "github" in final_state["relevant_apps"]
211
286
  assert "slack" in final_state["relevant_apps"]
@@ -219,7 +294,9 @@ class TestToolFinderGraph:
219
294
  """Test Case 5: Unavailable App"""
220
295
  task = "Create a new design file in Figma."
221
296
  graph = build_tool_node_graph(llm, registry)
222
- final_state = await graph.ainvoke({"task": task, "messages": [HumanMessage(content=task)]})
297
+ final_state = await graph.ainvoke(
298
+ {"task": task, "messages": [HumanMessage(content=task)]}
299
+ )
223
300
  assert final_state["apps_required"] is True
224
301
  assert not final_state["relevant_apps"]
225
302
  assert not final_state["apps_with_tools"]
@@ -229,7 +306,9 @@ class TestToolFinderGraph:
229
306
  """Test Case 6: No App Needed"""
230
307
  task = "hello"
231
308
  graph = build_tool_node_graph(llm, registry)
232
- final_state = await graph.ainvoke({"task": task, "messages": [HumanMessage(content=task)]})
309
+ final_state = await graph.ainvoke(
310
+ {"task": task, "messages": [HumanMessage(content=task)]}
311
+ )
233
312
  assert final_state["apps_required"] is False
234
313
 
235
314
 
@@ -272,7 +351,11 @@ class TestAgents:
272
351
  assert final_messages, "The agent should have produced at least one message."
273
352
  last_message = final_messages[-1]
274
353
 
275
- final_response = last_message.content if hasattr(last_message, "content") else str(last_message)
354
+ final_response = (
355
+ last_message.content
356
+ if hasattr(last_message, "content")
357
+ else str(last_message)
358
+ )
276
359
 
277
360
  # Print the response for manual verification and for the LLM judge
278
361
  print("\n--- Agent's Final Response ---")
@@ -22,7 +22,9 @@ async def build_graph(tool_registry: ToolRegistry, instructions: str = ""):
22
22
  tools_list = []
23
23
  if app_ids is not None:
24
24
  for app_id in app_ids:
25
- tools_list.extend(await tool_registry.search_tools(query, limit=10, app_id=app_id))
25
+ tools_list.extend(
26
+ await tool_registry.search_tools(query, limit=10, app_id=app_id)
27
+ )
26
28
  else:
27
29
  tools_list = await tool_registry.search_tools(query, limit=10)
28
30
  tools_list = [f"{tool['id']}: {tool['description']}" for tool in tools_list]
@@ -48,21 +50,33 @@ async def build_graph(tool_registry: ToolRegistry, instructions: str = ""):
48
50
  connections = await tool_registry.list_connected_apps()
49
51
  connection_ids = set([connection["app_id"] for connection in connections])
50
52
  connected_apps = [app["id"] for app in app_ids if app["id"] in connection_ids]
51
- unconnected_apps = [app["id"] for app in app_ids if app["id"] not in connection_ids]
52
- app_id_descriptions = "These are the apps connected to the user's account:\n" + "\n".join(
53
- [f"{app}" for app in connected_apps]
53
+ unconnected_apps = [
54
+ app["id"] for app in app_ids if app["id"] not in connection_ids
55
+ ]
56
+ app_id_descriptions = (
57
+ "These are the apps connected to the user's account:\n"
58
+ + "\n".join([f"{app}" for app in connected_apps])
54
59
  )
55
60
  if unconnected_apps:
56
61
  app_id_descriptions += "\n\nOther (not connected) apps: " + "\n".join(
57
62
  [f"{app}" for app in unconnected_apps]
58
63
  )
59
64
 
60
- system_prompt = system_prompt.format(system_time=datetime.now(tz=UTC).isoformat(), app_ids=app_id_descriptions)
65
+ system_prompt = system_prompt.format(
66
+ system_time=datetime.now(tz=UTC).isoformat(), app_ids=app_id_descriptions
67
+ )
61
68
 
62
- messages = [{"role": "system", "content": system_prompt + "\n" + instructions}, *state["messages"]]
69
+ messages = [
70
+ {"role": "system", "content": system_prompt + "\n" + instructions},
71
+ *state["messages"],
72
+ ]
63
73
  model = load_chat_model(runtime.context.model)
64
- loaded_tools = await tool_registry.export_tools(tools=state["selected_tool_ids"], format=ToolFormat.LANGCHAIN)
65
- model_with_tools = model.bind_tools([search_tools, ask_user, load_tools, *loaded_tools], tool_choice="auto")
74
+ loaded_tools = await tool_registry.export_tools(
75
+ tools=state["selected_tool_ids"], format=ToolFormat.LANGCHAIN
76
+ )
77
+ model_with_tools = model.bind_tools(
78
+ [search_tools, ask_user, load_tools, *loaded_tools], tool_choice="auto"
79
+ )
66
80
  response_raw = model_with_tools.invoke(messages)
67
81
  response = cast(AIMessage, response_raw)
68
82
  return {"messages": [response]}
@@ -102,7 +116,8 @@ async def build_graph(tool_registry: ToolRegistry, instructions: str = ""):
102
116
  tools = await search_tools.ainvoke(tool_call["args"])
103
117
  outputs.append(
104
118
  ToolMessage(
105
- content=json.dumps(tools) + "\n\nUse the load_tools tool to load the tools you want to use.",
119
+ content=json.dumps(tools)
120
+ + "\n\nUse the load_tools tool to load the tools you want to use.",
106
121
  name=tool_call["name"],
107
122
  tool_call_id=tool_call["id"],
108
123
  )
@@ -119,9 +134,13 @@ async def build_graph(tool_registry: ToolRegistry, instructions: str = ""):
119
134
  )
120
135
  )
121
136
  else:
122
- await tool_registry.export_tools([tool_call["name"]], ToolFormat.LANGCHAIN)
137
+ await tool_registry.export_tools(
138
+ [tool_call["name"]], ToolFormat.LANGCHAIN
139
+ )
123
140
  try:
124
- tool_result = await tool_registry.call_tool(tool_call["name"], tool_call["args"])
141
+ tool_result = await tool_registry.call_tool(
142
+ tool_call["name"], tool_call["args"]
143
+ )
125
144
  outputs.append(
126
145
  ToolMessage(
127
146
  content=json.dumps(tool_result),
@@ -8,7 +8,6 @@ tool_registry = AgentrRegistry()
8
8
  tool_manager = ToolManager()
9
9
 
10
10
 
11
-
12
11
  async def main():
13
12
  instructions = """
14
13
  You are a helpful assistant that can use tools to help the user. If a task requires multiple steps, you should perform separate different searches for different actions. Prefer completing one action before searching for another.
@@ -16,10 +15,5 @@ async def main():
16
15
  graph = await build_graph(tool_registry, instructions=instructions)
17
16
  return graph
18
17
 
19
- graph = asyncio.run(main())
20
-
21
-
22
-
23
-
24
-
25
18
 
19
+ graph = asyncio.run(main())
@@ -10,7 +10,14 @@ from .utils import RichCLI
10
10
 
11
11
 
12
12
  class BaseAgent:
13
- def __init__(self, name: str, instructions: str, model: str, memory: BaseCheckpointSaver | None = None, **kwargs):
13
+ def __init__(
14
+ self,
15
+ name: str,
16
+ instructions: str,
17
+ model: str,
18
+ memory: BaseCheckpointSaver | None = None,
19
+ **kwargs,
20
+ ):
14
21
  self.name = name
15
22
  self.instructions = instructions
16
23
  self.model = model
@@ -27,12 +34,26 @@ class BaseAgent:
27
34
  async def _build_graph(self):
28
35
  raise NotImplementedError("Subclasses must implement this method")
29
36
 
30
- async def stream(self, thread_id: str, user_input: str):
37
+ async def stream(self, thread_id: str, user_input: str, metadata: dict = None):
31
38
  await self.ainit()
32
39
  aggregate = None
40
+
41
+ run_metadata = {
42
+ "agent_name": self.name,
43
+ "is_background_run": False, # Default to False
44
+ }
45
+
46
+ if metadata:
47
+ run_metadata.update(metadata)
48
+
49
+ run_config = {
50
+ "configurable": {"thread_id": thread_id},
51
+ "metadata": run_metadata,
52
+ }
53
+
33
54
  async for event, metadata in self._graph.astream(
34
55
  {"messages": [{"role": "user", "content": user_input}]},
35
- config={"configurable": {"thread_id": thread_id}},
56
+ config=run_config,
36
57
  context={"system_prompt": self.instructions, "model": self.model},
37
58
  stream_mode="messages",
38
59
  stream_usage=True,
@@ -66,12 +87,28 @@ class BaseAgent:
66
87
  async for event in self.stream(thread_id, user_input):
67
88
  stream_updater.update(event.content)
68
89
 
69
- async def invoke(self, user_input: str, thread_id: str = str(uuid4())):
90
+ async def invoke(
91
+ self, user_input: str, thread_id: str = str(uuid4()), metadata: dict = None
92
+ ):
70
93
  """Run the agent"""
71
94
  await self.ainit()
95
+
96
+ run_metadata = {
97
+ "agent_name": self.name,
98
+ "is_background_run": False, # Default to False
99
+ }
100
+
101
+ if metadata:
102
+ run_metadata.update(metadata)
103
+
104
+ run_config = {
105
+ "configurable": {"thread_id": thread_id},
106
+ "metadata": run_metadata,
107
+ }
108
+
72
109
  return await self._graph.ainvoke(
73
110
  {"messages": [{"role": "user", "content": user_input}]},
74
- config={"configurable": {"thread_id": thread_id}},
111
+ config=run_config,
75
112
  context={"system_prompt": self.instructions, "model": self.model},
76
113
  )
77
114
 
@@ -85,10 +122,15 @@ class BaseAgent:
85
122
  # Main loop
86
123
  while True:
87
124
  try:
88
- state = self._graph.get_state(config={"configurable": {"thread_id": thread_id}})
125
+ state = self._graph.get_state(
126
+ config={"configurable": {"thread_id": thread_id}}
127
+ )
89
128
  if state.interrupts:
90
129
  value = self.cli.handle_interrupt(state.interrupts[0])
91
- self._graph.invoke(Command(resume=value), config={"configurable": {"thread_id": thread_id}})
130
+ self._graph.invoke(
131
+ Command(resume=value),
132
+ config={"configurable": {"thread_id": thread_id}},
133
+ )
92
134
  continue
93
135
 
94
136
  user_input = self.cli.get_user_input()
@@ -99,7 +141,9 @@ class BaseAgent:
99
141
  if user_input.startswith("/"):
100
142
  command = user_input.lower().lstrip("/")
101
143
  if command == "about":
102
- self.cli.display_info(f"Agent is {self.name}. {self.instructions}")
144
+ self.cli.display_info(
145
+ f"Agent is {self.name}. {self.instructions}"
146
+ )
103
147
  continue
104
148
  elif command == "exit" or command == "quit" or command == "q":
105
149
  self.cli.display_info("Goodbye! 👋")
@@ -110,7 +154,9 @@ class BaseAgent:
110
154
  thread_id = str(uuid4())
111
155
  continue
112
156
  elif command == "help":
113
- self.cli.display_info("Available commands: /about, /exit, /quit, /q, /reset")
157
+ self.cli.display_info(
158
+ "Available commands: /about, /exit, /quit, /q, /reset"
159
+ )
114
160
  continue
115
161
  else:
116
162
  self.cli.display_error(f"Unknown command: {command}")
@@ -27,7 +27,9 @@ class BigToolAgent(BaseAgent):
27
27
  self.llm = load_chat_model(self.model)
28
28
  self.tool_selection_llm = load_chat_model("gemini/gemini-2.0-flash-001")
29
29
 
30
- logger.info(f"BigToolAgent '{self.name}' initialized with model '{self.model}'.")
30
+ logger.info(
31
+ f"BigToolAgent '{self.name}' initialized with model '{self.model}'."
32
+ )
31
33
 
32
34
  async def _build_graph(self):
33
35
  """Build the bigtool agent graph using the existing create_agent function."""