universal-mcp-agents 0.1.17__tar.gz → 0.1.19__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 (76) hide show
  1. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/.github/workflows/evals.yml +0 -2
  2. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/.github/workflows/release-please.yml +0 -2
  3. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/.gitignore +0 -1
  4. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/GEMINI.md +1 -1
  5. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/PKG-INFO +1 -1
  6. universal_mcp_agents-0.1.19/bech.py +38 -0
  7. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/bump_and_release.sh +1 -4
  8. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/pyproject.toml +2 -2
  9. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/__init__.py +3 -1
  10. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/base.py +3 -0
  11. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/cli.py +0 -3
  12. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact0/__main__.py +0 -6
  13. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact0/llm_tool.py +1 -103
  14. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact0/playbook_agent.py +26 -30
  15. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact0/prompts.py +35 -96
  16. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact0/tools.py +34 -22
  17. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact0/utils.py +1 -1
  18. universal_mcp_agents-0.1.19/src/universal_mcp/agents/sandbox.py +90 -0
  19. universal_mcp_agents-0.1.19/src/universal_mcp/applications/filesystem/__init__.py +0 -0
  20. universal_mcp_agents-0.1.19/src/universal_mcp/applications/filesystem/app.py +160 -0
  21. universal_mcp_agents-0.1.19/src/universal_mcp/applications/llm/__init__.py +3 -0
  22. universal_mcp_agents-0.1.19/src/universal_mcp/applications/llm/app.py +300 -0
  23. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/uv.lock +291 -264
  24. universal_mcp_agents-0.1.17/src/universal_mcp/applications/llm/__init__.py +0 -3
  25. universal_mcp_agents-0.1.17/src/universal_mcp/applications/llm/app.py +0 -158
  26. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/.github/workflows/lint.yml +0 -0
  27. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/.github/workflows/tests.yml +0 -0
  28. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/.pre-commit-config.yaml +0 -0
  29. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/PROMPTS.md +0 -0
  30. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/README.md +0 -0
  31. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/evals/__init__.py +0 -0
  32. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/evals/dataset.py +0 -0
  33. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/evals/datasets/codeact.jsonl +0 -0
  34. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/evals/datasets/exact.jsonl +0 -0
  35. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/evals/datasets/tasks.jsonl +0 -0
  36. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/evals/evaluators.py +0 -0
  37. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/evals/prompts.py +0 -0
  38. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/evals/run.py +0 -0
  39. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/evals/utils.py +0 -0
  40. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/tests/test_agents.py +0 -0
  41. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/bigtool/__init__.py +0 -0
  42. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/bigtool/__main__.py +0 -0
  43. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/bigtool/agent.py +0 -0
  44. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/bigtool/context.py +0 -0
  45. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/bigtool/graph.py +0 -0
  46. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/bigtool/prompts.py +0 -0
  47. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/bigtool/state.py +0 -0
  48. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/bigtool/tools.py +0 -0
  49. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/builder/__main__.py +0 -0
  50. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/builder/builder.py +0 -0
  51. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/builder/helper.py +0 -0
  52. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/builder/prompts.py +0 -0
  53. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/builder/state.py +0 -0
  54. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact/__init__.py +0 -0
  55. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact/__main__.py +0 -0
  56. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact/agent.py +0 -0
  57. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact/models.py +0 -0
  58. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact/prompts.py +0 -0
  59. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact/sandbox.py +0 -0
  60. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact/state.py +0 -0
  61. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact/utils.py +0 -0
  62. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact0/__init__.py +0 -0
  63. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact0/agent.py +0 -0
  64. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact0/config.py +0 -0
  65. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact0/langgraph_agent.py +0 -0
  66. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact0/sandbox.py +0 -0
  67. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/codeact0/state.py +0 -0
  68. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/hil.py +0 -0
  69. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/llm.py +0 -0
  70. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/react.py +0 -0
  71. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/shared/__main__.py +0 -0
  72. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/shared/prompts.py +0 -0
  73. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/shared/tool_node.py +0 -0
  74. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/simple.py +0 -0
  75. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/agents/utils.py +0 -0
  76. {universal_mcp_agents-0.1.17 → universal_mcp_agents-0.1.19}/src/universal_mcp/applications/ui/app.py +0 -0
@@ -57,5 +57,3 @@ jobs:
57
57
  name: eval-artifacts-${{ matrix.agent }}-${{ matrix.evaluator }}
58
58
  path: dist/evals/${{ matrix.agent }}/${{ matrix.evaluator }}
59
59
  if-no-files-found: warn
60
-
61
-
@@ -63,5 +63,3 @@ jobs:
63
63
  git add CHANGELOG.md || true
64
64
  git commit -m "update CHANGELOG.md with author info [skip ci] [skip release]" || true
65
65
  git push || true
66
-
67
-
@@ -58,4 +58,3 @@ site/
58
58
  .langgraph_api/
59
59
  langgraph.json
60
60
  agentr-1-6c4ebd5cc914.json
61
-
@@ -48,4 +48,4 @@ Common commands (copy/paste)
48
48
 
49
49
 
50
50
 
51
- NEVER commit or push changes without asking explicitly.
51
+ NEVER commit or push changes without asking explicitly.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp-agents
3
- Version: 0.1.17
3
+ Version: 0.1.19
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
@@ -0,0 +1,38 @@
1
+ from universal_mcp.agentr.registry import AgentrRegistry
2
+ from universal_mcp.agents import get_agent
3
+ from langgraph.checkpoint.memory import MemorySaver
4
+ from universal_mcp.agents.utils import messages_to_list
5
+ import time
6
+ from loguru import logger
7
+
8
+
9
+ async def main():
10
+ start_time = time.time()
11
+ memory = MemorySaver()
12
+ logger.info(f"Checkpointer: Time consumed: {time.time() - start_time}")
13
+ agent_cls = get_agent("codeact-repl")
14
+ logger.info(f"Get class: Time consumed: {time.time() - start_time}")
15
+ registry = AgentrRegistry()
16
+ logger.info(f"Init Registry: Time consumed: {time.time() - start_time}")
17
+ agent = agent_cls(
18
+ name="CodeAct Agent",
19
+ instructions="Be very concise in your answers.",
20
+ model="anthropic:claude-4-sonnet-20250514",
21
+ tools={},
22
+ registry=registry,
23
+ memory=memory,
24
+ )
25
+ logger.info(f"Create agent: Time consumed: {time.time() - start_time}")
26
+ print("Init agent...")
27
+ await agent.ainit()
28
+ logger.info(f"Init Agent: Time consumed: {time.time() - start_time}")
29
+ print("Starting agent...")
30
+ async for e in agent.stream(user_input="hi"):
31
+ logger.info(f"First token: Time consumed: {time.time() - start_time}")
32
+ print(e)
33
+
34
+
35
+ if __name__ == "__main__":
36
+ import asyncio
37
+
38
+ asyncio.run(main())
@@ -9,7 +9,7 @@ uv sync --all-extras
9
9
 
10
10
  # Run tests with pytest
11
11
  echo "Running tests with pytest..."
12
- # uv run pytest
12
+ uv run pytest
13
13
 
14
14
  echo "Tests passed!"
15
15
 
@@ -91,6 +91,3 @@ if [ "$1" = "release" ]; then
91
91
  else
92
92
  echo "Skipping release steps"
93
93
  fi
94
-
95
-
96
-
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "universal-mcp-agents"
9
- version = "0.1.17"
9
+ version = "0.1.19"
10
10
  description = "Add your description here"
11
11
  readme = "README.md"
12
12
  authors = [
@@ -70,7 +70,7 @@ lint.ignore = [
70
70
 
71
71
  [tool.ruff.lint.pylint]
72
72
  max-args = 10
73
- max-statements = 85
73
+ max-statements = 118
74
74
  max-returns = 10
75
75
  max-branches = 37
76
76
 
@@ -9,7 +9,9 @@ from universal_mcp.agents.react import ReactAgent
9
9
  from universal_mcp.agents.simple import SimpleAgent
10
10
 
11
11
 
12
- def get_agent(agent_name: Literal["react", "simple", "builder", "bigtool", "codeact-script", "codeact-repl"]):
12
+ def get_agent(
13
+ agent_name: Literal["react", "simple", "builder", "bigtool", "codeact-script", "codeact-repl"],
14
+ ):
13
15
  if agent_name == "react":
14
16
  return ReactAgent
15
17
  elif agent_name == "simple":
@@ -49,8 +49,11 @@ class BaseAgent:
49
49
  run_metadata.update(metadata)
50
50
 
51
51
  run_config = {
52
+ "recursion_limit": 25,
52
53
  "configurable": {"thread_id": thread_id},
53
54
  "metadata": run_metadata,
55
+ "run_id": thread_id,
56
+ "run_name": self.name,
54
57
  }
55
58
 
56
59
  async for event, meta in self._graph.astream(
@@ -28,9 +28,6 @@ def run(name: str = "react"):
28
28
  "model": "anthropic/claude-sonnet-4-20250514",
29
29
  "registry": AgentrRegistry(client=client),
30
30
  "memory": MemorySaver(),
31
- "tools": {
32
- "google_mail": ["send_email"],
33
- },
34
31
  }
35
32
  agent_cls = get_agent(name)
36
33
  agent = agent_cls(name=name, **params)
@@ -19,12 +19,6 @@ async def main():
19
19
  memory=memory,
20
20
  )
21
21
  print("Starting agent...")
22
- # await agent.ainit()
23
- # await agent.run_interactive()
24
- # async for event in agent.stream(
25
- # user_input="Fetch unsubscribe links from my Gmail inbox for promo emails I have received in the last 7 days"
26
- # ):
27
- # print(event.content, end="")
28
22
  result = await agent.invoke(
29
23
  user_input="Fetch unsubscribe links from my Gmail inbox for promo emails I have received in the last 7 days"
30
24
  )
@@ -27,7 +27,7 @@ def smart_print(data: Any) -> None:
27
27
  Args:
28
28
  data: Either a dictionary with string keys, or a list of such dictionaries
29
29
  """
30
- print(light_copy(data)) # noqa
30
+ print(light_copy(data)) # noqa: T201
31
31
 
32
32
 
33
33
  def creative_writer(
@@ -275,105 +275,3 @@ def data_extractor(
275
275
  .invoke(prompt)
276
276
  )
277
277
  return cast(dict[str, Any], response)
278
-
279
-
280
- # news_articles_schema = {
281
- # "type": "object",
282
- # "properties": {
283
- # "articles": {
284
- # "type": "array",
285
- # "title": "Articles",
286
- # "description": "List of news articles",
287
- # "items": {
288
- # "type": "object",
289
- # "properties": {
290
- # "headline": {
291
- # "type": "string",
292
- # "title": "Headline",
293
- # "description": "The headline of the news article"
294
- # },
295
- # "url": {
296
- # "type": "string",
297
- # "title": "URL",
298
- # "description": "The URL of the news article"
299
- # }
300
- # },
301
- # "required": ["headline", "url"],
302
- # }
303
- # }
304
- # },
305
- # "required": ["articles"],
306
- # }
307
-
308
-
309
- # news_articles_schema = {
310
- # "title": "NewsArticleList",
311
- # "description": "A list of news articles with headlines and URLs",
312
- # "type": "object",
313
- # "properties": {
314
- # "articles": {
315
- # "type": "array",
316
- # "items": {
317
- # "type": "object",
318
- # "properties": {
319
- # "headline": {
320
- # "type": "string"
321
- # },
322
- # "url": {
323
- # "type": "string"
324
- # }
325
- # },
326
- # "required": ["headline", "url"]
327
- # }
328
- # }
329
- # },
330
- # "required": ["articles"]
331
- # }
332
- # model = init_chat_model(model="claude-4-sonnet-20250514", temperature=0)
333
- # structured_model = model.with_structured_output(news_articles_schema)
334
-
335
-
336
- # class TwitterComment(BaseModel):
337
- # skip: bool
338
- # reason: str
339
- # comment: str
340
-
341
- # twitter_comment_schema = {
342
- # "title": "TwitterComment",
343
- # "description": "A twitter comment to engage with followers",
344
- # "type": "object",
345
- # "properties": {
346
- # "skip": {
347
- # "type": "boolean"
348
- # },
349
- # "reason": {
350
- # "type": "string"
351
- # },
352
- # "comment": {
353
- # "type": "string"
354
- # },
355
- # "tagged_profiles": {
356
- # "type": "array",
357
- # "items": {
358
- # "type": "string"
359
- # }
360
- # }
361
- # },
362
- # "required": ["skip", "reason"]
363
- # }
364
-
365
- # comment = {
366
- # "tweet_id": "08109402",
367
- # "handle": "@iamnishant",
368
- # "text": "Hey really loved this tweet! Well said 💯"
369
- # }
370
-
371
- # comment_instructions = (
372
- # "Goal is to engage with my twitter followers who have commented on my tweets."
373
- # "Please generate a single line, context-aware, conversational reply for the given comment."
374
- # "- Use social media language (can use hinglish)."
375
- # "- Skip the reply, if the comment is too generic."
376
- # "- Also tag relevant people in the reply."
377
- # )
378
-
379
- # my_reply = call_llm(comment_instructions, comment, twitter_comment_schema)
@@ -2,18 +2,16 @@ import inspect
2
2
  import json
3
3
  import re
4
4
  from collections.abc import Callable
5
- from dataclasses import dataclass
6
- from pathlib import Path
7
5
  from typing import Literal, cast
8
6
 
9
- from langchain_core.messages import AIMessage, RemoveMessage, ToolMessage
7
+ from langchain_core.messages import AIMessage, ToolMessage
10
8
  from langchain_core.tools import StructuredTool
11
9
  from langchain_core.tools import tool as create_tool
12
10
  from langgraph.checkpoint.base import BaseCheckpointSaver
13
11
  from langgraph.graph import START, StateGraph
14
12
  from langgraph.types import Command, RetryPolicy
15
13
  from universal_mcp.tools.registry import ToolRegistry
16
- from universal_mcp.types import ToolFormat, ToolConfig
14
+ from universal_mcp.types import ToolConfig, ToolFormat
17
15
 
18
16
  from universal_mcp.agents.base import BaseAgent
19
17
  from universal_mcp.agents.codeact0.llm_tool import ai_classify, call_llm, data_extractor, smart_print
@@ -22,10 +20,14 @@ from universal_mcp.agents.codeact0.prompts import (
22
20
  )
23
21
  from universal_mcp.agents.codeact0.sandbox import eval_unsafe, execute_ipython_cell
24
22
  from universal_mcp.agents.codeact0.state import CodeActState
25
- from universal_mcp.agents.codeact0.tools import create_meta_tools, enter_playbook_mode, exit_playbook_mode, get_valid_tools
23
+ from universal_mcp.agents.codeact0.tools import (
24
+ create_meta_tools,
25
+ enter_playbook_mode,
26
+ get_valid_tools,
27
+ )
26
28
  from universal_mcp.agents.codeact0.utils import inject_context, smart_truncate
27
29
  from universal_mcp.agents.llm import load_chat_model
28
- from universal_mcp.agents.utils import filter_retry_on, get_message_text, convert_tool_ids_to_dict
30
+ from universal_mcp.agents.utils import convert_tool_ids_to_dict, filter_retry_on, get_message_text
29
31
 
30
32
  PLAYBOOK_PLANNING_PROMPT = """Now, you are tasked with creating a reusable playbook from the user's previous workflow.
31
33
 
@@ -48,7 +50,6 @@ Example:
48
50
  Now create a plan based on the conversation history. Enclose it between ``` and ```. Ask the user if the plan is okay."""
49
51
 
50
52
 
51
-
52
53
  PLAYBOOK_CONFIRMING_PROMPT = """Now, you are tasked with confirming the playbook plan. Return True if the user is happy with the plan, False otherwise. Do not say anything else in your response. The user response will be the last message in the chain.
53
54
  """
54
55
 
@@ -80,7 +81,7 @@ class CodeActPlaybookAgent(BaseAgent):
80
81
  memory=memory,
81
82
  **kwargs,
82
83
  )
83
- self.model_instance = load_chat_model(model, thinking=True)
84
+ self.model_instance = load_chat_model(model)
84
85
  self.tools_config = tools or []
85
86
  self.registry = registry
86
87
  self.playbook_registry = playbook_registry
@@ -92,20 +93,19 @@ class CodeActPlaybookAgent(BaseAgent):
92
93
  meta_tools = create_meta_tools(self.registry)
93
94
  additional_tools = [smart_print, data_extractor, ai_classify, call_llm, meta_tools["web_search"]]
94
95
  self.additional_tools = [t if isinstance(t, StructuredTool) else create_tool(t) for t in additional_tools]
96
+
95
97
  async def call_model(state: CodeActState) -> Command[Literal["sandbox", "execute_tools"]]:
96
98
  self.exported_tools = []
97
99
  if self.tools_config:
98
100
  # Convert dict format to list format if needed
99
101
  if isinstance(self.tools_config, dict):
100
102
  self.tools_config = [
101
- f"{provider}__{tool}"
102
- for provider, tools in self.tools_config.items()
103
- for tool in tools
103
+ f"{provider}__{tool}" for provider, tools in self.tools_config.items() for tool in tools
104
104
  ]
105
105
  if not self.registry:
106
106
  raise ValueError("Tools are configured but no registry is provided")
107
107
  # Langchain tools are fine
108
- self.tools_config.extend(state.get('selected_tool_ids',[]))
108
+ self.tools_config.extend(state.get("selected_tool_ids", []))
109
109
  self.exported_tools = await self.registry.export_tools(self.tools_config, ToolFormat.LANGCHAIN)
110
110
  self.final_instructions, self.tools_context = create_default_prompt(
111
111
  self.exported_tools, self.additional_tools, self.instructions
@@ -167,7 +167,7 @@ class CodeActPlaybookAgent(BaseAgent):
167
167
  )
168
168
  return Command(
169
169
  goto="playbook",
170
- update={"playbook_mode": "planning", "messages": [tool_message]}, #Entered Playbook mode
170
+ update={"playbook_mode": "planning", "messages": [tool_message]}, # Entered Playbook mode
171
171
  )
172
172
  elif tool_call["name"] == "execute_ipython_cell":
173
173
  return Command(goto="sandbox")
@@ -184,8 +184,13 @@ class CodeActPlaybookAgent(BaseAgent):
184
184
  ai_msg = f"Please login to the following app(s) using the following links and let me know in order to proceed:\n {links} "
185
185
  elif tool_call["name"] == "search_functions":
186
186
  tool_result = await meta_tools["search_functions"].ainvoke(tool_call["args"])
187
+ else:
188
+ raise Exception(
189
+ f"Unexpected tool call: {tool_call['name']}. "
190
+ "tool calls must be one of 'enter_playbook_mode', 'execute_ipython_cell', 'load_functions', or 'search_functions'"
191
+ )
187
192
  except Exception as e:
188
- tool_result = f"Error during {tool_call}: {e}"
193
+ tool_result = str(e)
189
194
 
190
195
  tool_message = ToolMessage(
191
196
  content=json.dumps(tool_result),
@@ -261,13 +266,12 @@ class CodeActPlaybookAgent(BaseAgent):
261
266
  response = cast(AIMessage, response)
262
267
  response_text = get_message_text(response)
263
268
  # Extract plan from response text between triple backticks
264
- plan_match = re.search(r'```(.*?)```', response_text, re.DOTALL)
269
+ plan_match = re.search(r"```(.*?)```", response_text, re.DOTALL)
265
270
  if plan_match:
266
271
  plan = plan_match.group(1).strip()
267
272
  else:
268
273
  plan = response_text.strip()
269
274
  return Command(update={"messages": [response], "playbook_mode": "confirming", "plan": plan})
270
-
271
275
 
272
276
  elif playbook_mode == "confirming":
273
277
  confirmation_instructions = self.instructions + PLAYBOOK_CONFIRMING_PROMPT
@@ -279,8 +283,6 @@ class CodeActPlaybookAgent(BaseAgent):
279
283
  else:
280
284
  return Command(goto="playbook", update={"playbook_mode": "planning"})
281
285
 
282
-
283
-
284
286
  elif playbook_mode == "generating":
285
287
  generating_instructions = self.instructions + PLAYBOOK_GENERATING_PROMPT
286
288
  messages = [{"role": "system", "content": generating_instructions}] + state["messages"]
@@ -327,25 +329,19 @@ class CodeActPlaybookAgent(BaseAgent):
327
329
  saved_note = f"Failed to save generated playbook as Agent '{function_name}': {e}"
328
330
 
329
331
  # Mock tool call for exit_playbook_mode (for testing/demonstration)
330
- mock_exit_tool_call = {
331
- "name": "exit_playbook_mode",
332
- "args": {},
333
- "id": "mock_exit_playbook_123"
334
- }
335
- mock_assistant_message = AIMessage(
336
- content=saved_note,
337
- tool_calls=[mock_exit_tool_call]
338
- )
332
+ mock_exit_tool_call = {"name": "exit_playbook_mode", "args": {}, "id": "mock_exit_playbook_123"}
333
+ mock_assistant_message = AIMessage(content=saved_note, tool_calls=[mock_exit_tool_call])
339
334
 
340
-
341
335
  # Mock tool response for exit_playbook_mode
342
336
  mock_exit_tool_response = ToolMessage(
343
337
  content=json.dumps(f"Exited Playbook Mode.{saved_note}"),
344
338
  name="exit_playbook_mode",
345
- tool_call_id="mock_exit_playbook_123"
339
+ tool_call_id="mock_exit_playbook_123",
346
340
  )
347
341
 
348
- return Command(update={"messages": [mock_assistant_message, mock_exit_tool_response], "playbook_mode": "normal"})
342
+ return Command(
343
+ update={"messages": [mock_assistant_message, mock_exit_tool_response], "playbook_mode": "normal"}
344
+ )
349
345
 
350
346
  def route_entry(state: CodeActState) -> Literal["call_model", "playbook"]:
351
347
  """Route to either normal mode or playbook creation"""
@@ -9,102 +9,41 @@ from universal_mcp.agents.codeact0.utils import schema_to_signature
9
9
  uneditable_prompt = """
10
10
  You are **Wingmen**, an AI Assistant created by AgentR — a creative, straight-forward, and direct principal software engineer with access to tools.
11
11
 
12
- ## Responsibilities
13
-
14
- - **Answer directly** if the task is simple (e.g. print, math, general knowledge).
15
- - For any task requiring logic, execution, or data handling, use `execute_ipython_cell`.
16
- - For writing or NLP tasks (summarizing, generating, extracting), always use AI functions via code never respond directly.
17
-
18
- ## Tool vs. Function: Required Separation
19
-
20
- You must clearly distinguish between tools (called via the tool calling API) and internal functions (used inside code blocks).
21
-
22
- ### Tools Must Be Called via Tool Calling API
23
-
24
- These must be called using **tool calling**, not from inside code blocks:
25
-
26
- - `execute_ipython_cell` For running any Python code or logic.
27
- - `search_functions` To discover available functions for a task.
28
- - `load_functions` To load a specific function by full ID.
29
-
30
- **Do not attempt to call these inside `python` code.**
31
- Use tool calling syntax for these operations.
32
-
33
- ### Functions Must Be Used Inside Code Blocks
34
-
35
- All other functions, including LLM functions, must always be used within code executed by `execute_ipython_cell`. These include:
36
-
37
- - `smart_print()` — For inspecting unknown data structures before looping.
38
- - `asyncio.run()` — For wrapping and executing asynchronous logic. You must not use await outside an async function. And the async function must be called by `asyncio.run()`.
39
- - Any functions for applications loaded via `load_functions`.
40
- - Any logic, data handling, writing, NLP, generation, summarization, or extraction functionality of LLMs.
41
-
42
- These must be called **inside a Python code block**, and that block must be executed using `execute_ipython_cell`.
43
-
44
- ## Tool/Function Usage Policy
45
-
46
- 1. **Always Use Tools/Functions for Required Tasks**
47
- Any searching, loading, or executing must be done using a tool/function call. Never answer manually if a tool/function is appropriate.
48
-
49
- 2. **Use Existing Functions First**
50
- Use existing functions if available. Otherwise, use `search_functions` with a concise query describing the task.
51
-
52
- 3. **Load Only Relevant Tools**
53
- When calling `load_functions`, include only relevant function IDs.
54
- - Prefer connected applications over unconnected ones.
55
- - If multiple functions match (i.e. if none are connected, or multiple are connected), ask the user to choose.
56
- - After loading a tool, you do not need to import/declare it again. It can be called directly in further cells.
57
-
58
- 4. **Follow First Turn Process Strictly**
59
- On the **first turn**, do only **one** of the following:
60
- - Handle directly (if trivial)
61
- - Use a tool/function (`execute_ipython_cell`, `search_functions`, etc.)
62
-
63
- **Do not extend the conversation on the first message.**
64
-
65
- ## Coding Rules
66
-
67
- - Use `smart_print()` to inspect unknown structures, especially those received from function outputs, before looping or branching.
68
- - Validate logic with a single item before processing lists or large inputs.
69
- - Try to achieve as much as possible in a single code block.
70
- - Use only pre-installed Python libraries. Do import them once before using.
71
- - Outer level functions, variables, classes, and imports declared previously can be used in later cells.
72
- - For all functions, call using keyword arguments only. DO NOT use any positional arguments.
73
-
74
- ### **Async Function Usage — Critical**
75
-
76
- When calling asynchronous functions:
77
- - You must define or use an **inner async function**.
78
- - Use `await` only **inside** that async function.
79
- - Run it using `asyncio.run(<function_name>())` **without** `await` at the outer level.
80
-
81
- **Wrong - Using `await` outside an async function**
82
- ```
83
- result = await some_async_function()
84
- ```
85
- **Wrong - Attaching await before asyncio.run**.
86
- `await asyncio.run(main())`
87
- These will raise SyntaxError: 'await' outside async function
88
- The correct method is the following-
89
- ```
90
- import asyncio
91
- async def some_async_function():
92
- ...
93
-
94
- async def main():
95
- result = await some_async_function()
96
- print(result)
97
-
98
- asyncio.run(main())
99
- #or
100
- result = asyncio.run(some_async_function(arg1 = <arg1>))
101
- ```
102
- ## Output Formatting
103
- - All code results must be returned in **Markdown**.
104
- - The user cannot see raw output, so format results clearly:
105
- - Use tables for structured data.
106
- - Provide links for files or images.
107
- - Be explicit in formatting to ensure readability.
12
+ Your job is to answer the user's question or perform the task they ask for.
13
+ - Answer simple questions (which do not require you to write any code or access any external resources) directly. Note that any operation that involves using ONLY print functions should be answered directly.
14
+ - For task requiring operations or access to external resources, you should achieve the task by executing Python code snippets.
15
+ - You have access to `execute_ipython_cell` tool that allows you to execute Python code in an IPython notebook cell.
16
+ - You also have access to two tools for finding and loading more python functions- `search_functions` and `load_functions`, which you must use for finding functions for using different external applications.
17
+ - Prefer pre-loaded or functions already available when possible.
18
+ - Prioritize connected applications over unconnected ones from the output of `search_functions`.
19
+ - When multiple apps are connected, or none of the apps are connected, ask the user to choose the application(s).
20
+ - In writing or natural language processing tasks DO NOT answer directly. Instead use `execute_ipython_cell` tool with the AI functions provided to you for tasks like summarizing, text generation, classification, data extraction from text or unstructured data, etc. Avoid hardcoded approaches to classification, data extraction.
21
+ - The code you write will be executed in a sandbox environment, and you can use the output of previous executions in your code. variables, functions, imports are retained.
22
+ - Read and understand the output of the previous code snippet and use it to answer the user's request. Note that the code output is NOT visible to the user, so after the task is complete, you have to give the output to the user in a markdown format.
23
+ - If needed, feel free to ask for more information from the user (without using the `execute_ipython_cell` tool) to clarify the task.
24
+
25
+ GUIDELINES for writing code:
26
+ - Variables defined at the top level of previous code snippets can be referenced in your code.
27
+ - External functions which return a dict or list[dict] are ambiguous. Therefore, you MUST explore the structure of the returned data using `smart_print()` statements before using it, printing keys and values. `smart_print` truncates long strings from data, preventing huge output logs.
28
+ - When an operation involves running a fixed set of steps on a list of items, run one run correctly and then use a for loop to run the steps on each item in the list.
29
+ - In a single code snippet, try to achieve as much as possible.
30
+ - You can only import libraries that come pre-installed with Python. For external functions, use the search and load tools to access them in the code.
31
+ - For displaying final results to the user, you must present your output in markdown format, including image links, so that they are rendered and displayed to the user. The code output is NOT visible to the user.
32
+ - Call all functions using keyword arguments only, never positional arguments.
33
+ - Async Functions (Critical): Use them only as follows-
34
+ Case 1: Top-level await without asyncio.run()
35
+ Wrap in async function and call with asyncio.run():
36
+ async def main():
37
+ result = await some_async_function()
38
+ return result
39
+ asyncio.run(main())
40
+ Case 2: Using asyncio.run() directly
41
+ If code already contains asyncio.run(), use as-is — do not wrap again:
42
+ asyncio.run(some_async_function())
43
+ Rules:
44
+ - Never use await outside an async function
45
+ - Never use await asyncio.run()
46
+ - Never nest asyncio.run() calls
108
47
  """
109
48
 
110
49