universal-mcp-agents 0.1.12__py3-none-any.whl → 0.1.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of universal-mcp-agents might be problematic. Click here for more details.

Files changed (31) hide show
  1. universal_mcp/agents/__init__.py +1 -1
  2. universal_mcp/agents/base.py +2 -0
  3. universal_mcp/agents/bigtool/__init__.py +1 -1
  4. universal_mcp/agents/bigtool/agent.py +2 -2
  5. universal_mcp/agents/bigtool/graph.py +65 -31
  6. universal_mcp/agents/bigtool/prompts.py +2 -2
  7. universal_mcp/agents/bigtool/tools.py +18 -4
  8. universal_mcp/agents/builder/__main__.py +105 -30
  9. universal_mcp/agents/builder/builder.py +149 -160
  10. universal_mcp/agents/builder/helper.py +73 -0
  11. universal_mcp/agents/builder/prompts.py +33 -152
  12. universal_mcp/agents/builder/state.py +1 -1
  13. universal_mcp/agents/cli.py +2 -2
  14. universal_mcp/agents/codeact/agent.py +1 -1
  15. universal_mcp/agents/codeact/sandbox.py +1 -5
  16. universal_mcp/agents/codeact0/agent.py +5 -4
  17. universal_mcp/agents/codeact0/langgraph_agent.py +17 -0
  18. universal_mcp/agents/codeact0/llm_tool.py +1 -1
  19. universal_mcp/agents/codeact0/prompts.py +34 -23
  20. universal_mcp/agents/codeact0/usecases/11-github.yaml +6 -5
  21. universal_mcp/agents/codeact0/utils.py +42 -63
  22. universal_mcp/agents/shared/__main__.py +43 -0
  23. universal_mcp/agents/shared/prompts.py +50 -99
  24. universal_mcp/agents/shared/tool_node.py +149 -203
  25. universal_mcp/agents/utils.py +65 -0
  26. universal_mcp/applications/ui/app.py +2 -2
  27. {universal_mcp_agents-0.1.12.dist-info → universal_mcp_agents-0.1.14.dist-info}/METADATA +1 -1
  28. {universal_mcp_agents-0.1.12.dist-info → universal_mcp_agents-0.1.14.dist-info}/RECORD +29 -28
  29. universal_mcp/agents/codeact0/langgraph_graph.py +0 -17
  30. universal_mcp/agents/codeact0/legacy_codeact.py +0 -104
  31. {universal_mcp_agents-0.1.12.dist-info → universal_mcp_agents-0.1.14.dist-info}/WHEEL +0 -0
@@ -1,42 +1,26 @@
1
1
  import json
2
- from collections import defaultdict
3
2
 
4
- from langchain_core.language_models import BaseChatModel
5
- from langchain_core.messages import AIMessage, HumanMessage
3
+ from langchain_core.messages import HumanMessage
6
4
  from langgraph.checkpoint.base import BaseCheckpointSaver
7
5
  from langgraph.graph import END, START, StateGraph
8
6
  from loguru import logger
9
7
  from universal_mcp.tools.registry import ToolRegistry
10
8
  from universal_mcp.types import ToolConfig
9
+ from langgraph.types import Command
11
10
 
12
11
  from universal_mcp.agents.base import BaseAgent
13
12
  from universal_mcp.agents.builder.prompts import (
14
- AGENT_BUILDER_INSTRUCTIONS,
15
- AGENT_FROM_CONVERSATION_PROMPT,
16
- TASK_SYNTHESIS_PROMPT,
13
+ NEW_AGENT_PROMPT,
14
+ MODIFY_AGENT_PROMPT,
17
15
  )
18
16
  from universal_mcp.agents.builder.state import Agent, BuilderState
19
17
  from universal_mcp.agents.llm import load_chat_model
20
18
  from universal_mcp.agents.shared.tool_node import build_tool_node_graph
21
-
22
-
23
- async def generate_agent(llm: BaseChatModel, task: str, old_agent: Agent | None = None) -> Agent:
24
- """Generates an agent from a task, optionally modifying an existing one."""
25
- prompt_parts = [AGENT_BUILDER_INSTRUCTIONS]
26
- if old_agent:
27
- prompt_parts.append(
28
- "\nThe user wants to modify the following agent design. "
29
- "Incorporate their feedback into a new design.\n\n"
30
- f"**User Feedback:** {task}\n\n"
31
- f"{old_agent.model_dump_json(indent=2)}"
32
- )
33
- else:
34
- prompt_parts.append(f"\n\n**Task:** {task}")
35
-
36
- prompt = "\n".join(prompt_parts)
37
- structured_llm = llm.with_structured_output(Agent)
38
- agent = await structured_llm.ainvoke(prompt)
39
- return agent
19
+ from universal_mcp.agents.builder.helper import (
20
+ _extract_tools_from_history,
21
+ _clean_conversation_history,
22
+ _merge_tool_configs,
23
+ )
40
24
 
41
25
 
42
26
  class BuilderAgent(BaseAgent):
@@ -57,169 +41,174 @@ class BuilderAgent(BaseAgent):
57
41
  **kwargs,
58
42
  )
59
43
  self.registry = registry
60
- self.llm: BaseChatModel = load_chat_model(model, thinking=False)
44
+ self.llm = load_chat_model(model, thinking=False)
61
45
 
62
- def _entry_point_router(self, state: BuilderState):
46
+ async def invoke(
47
+ self,
48
+ thread_id: str,
49
+ user_input: dict,
50
+ ):
63
51
  """
64
- Determines the entry point of the graph based on the input format and conversation history.
65
- - If input is a JSON with 'conversation_history', it builds from the conversation.
66
- - If an agent has already been generated, it assumes a modification is requested.
67
- - Otherwise, it starts a fresh build from a text prompt.
52
+ Overrides BaseAgent.invoke to build or modify an agent.
53
+ This is the primary entry point for the Builder Agent.
68
54
  """
69
- last_message_content = state["messages"][-1].content
70
- try:
71
- # Case 1: Input is a JSON for building from a conversation
72
- payload = json.loads(last_message_content)
73
- if isinstance(payload, dict) and "conversation_history" in payload and "tool_config" in payload:
74
- logger.info("Routing to: build from conversation history.")
75
- return "synthesize_from_conversation"
76
- except (json.JSONDecodeError, TypeError):
77
- # Input is not a valid JSON, proceed as an interactive build
78
- pass
79
-
80
- # Case 2: It's an interactive build, check for modification vs. new
81
- if state.get("generated_agent"):
82
- logger.info("Routing to: modify existing agent.")
83
- return "synthesize_new_task"
84
- else:
85
- logger.info("Routing to: new agent build.")
86
- return "prepare_for_build"
87
-
88
- async def _prepare_for_build(self, state: BuilderState):
89
- """Sets the initial user task to begin the build process."""
90
- last_message = state["messages"][-1]
91
- task = last_message.content
92
- yield {
93
- "user_task": task,
94
- }
95
-
96
- async def _create_agent(self, state: BuilderState):
97
- """Creates or updates the agent definition from a user_task."""
98
- task = state["user_task"]
99
- agent = state.get("generated_agent")
55
+ keys = ("userInput", "agent", "tools", "messages")
56
+ userInput, agent_data, tools, messages = (user_input.get(k) for k in keys)
57
+ agent = Agent(**agent_data) if agent_data else None
58
+
59
+ await self.ainit()
60
+ graph = self._graph
61
+
62
+ initial_state = BuilderState(
63
+ user_task=userInput,
64
+ generated_agent=agent,
65
+ tool_config=tools,
66
+ messages=[],
67
+ )
100
68
 
101
- generated_agent = await generate_agent(self.llm, task, agent)
102
- yield {
103
- "generated_agent": generated_agent,
104
- }
69
+ if messages:
70
+ initial_state["messages"] = [HumanMessage(content=json.dumps(messages))]
71
+ elif not userInput and not agent:
72
+ raise ValueError("Either 'user_input' or 'messages' must be provided for a new agent.")
105
73
 
106
- async def _get_tool_config_for_task(self, task: str) -> ToolConfig:
107
- """Helper method to find and configure tools for a given task string."""
108
- tool_finder_graph = build_tool_node_graph(self.llm, self.registry)
109
- initial_state = {
110
- "original_task": task,
111
- "decomposition_attempts": 0,
112
- }
113
- final_state = await tool_finder_graph.ainvoke(initial_state)
114
- execution_plan = final_state.get("execution_plan")
115
74
 
116
- if not execution_plan:
117
- return {}
75
+ run_metadata = { "agent_name": self.name, "is_background_run": False }
118
76
 
119
- apps_with_tools = defaultdict(list)
120
- for step in execution_plan:
121
- app_id = step.get("app_id")
122
- tool_ids = step.get("tool_ids")
123
- if app_id and tool_ids:
124
- apps_with_tools[app_id].extend(tool_ids)
125
-
126
- return {app_id: list(set(tools)) for app_id, tools in apps_with_tools.items()}
127
-
128
- async def _create_tool_config(self, state: BuilderState):
129
- """Creates the tool configuration for the agent."""
130
- task = state["user_task"]
131
- tool_config = await self._get_tool_config_for_task(task)
132
- yield {
133
- "tool_config": tool_config,
77
+ config = {
78
+ "configurable": {"thread_id": thread_id},
79
+ "metadata": run_metadata,
80
+ "run_id": thread_id,
81
+ "run_name" : self.name
134
82
  }
135
83
 
136
- async def _synthesize_new_task_from_feedback(self, state: BuilderState):
137
- """Synthesizes a new user_task from the original task and subsequent user feedback."""
138
- original_task = next((msg.content for msg in state["messages"] if isinstance(msg, HumanMessage)), None)
139
- modification_request = state["messages"][-1].content
140
-
141
- if not original_task:
142
- raise ValueError("Could not find the original task in the conversation history.")
143
-
144
- synthesis_prompt = TASK_SYNTHESIS_PROMPT.format(
145
- original_task=original_task,
146
- modification_request=modification_request,
147
- )
148
-
149
- response = await self.llm.ainvoke(synthesis_prompt)
150
- new_synthesized_task = response.content.strip()
151
- logger.info(f"The new synthesized task is: {new_synthesized_task}")
152
- yield {
153
- "user_task": new_synthesized_task,
154
- }
84
+ final_state = await graph.ainvoke(initial_state, config=config)
85
+ return final_state
155
86
 
156
- async def _synthesize_from_conversation(self, state: BuilderState):
87
+ def _entry_point_router(self, state: BuilderState):
157
88
  """
158
- Takes conversation history and used tools from input to synthesize a complete agent profile.
159
- This is a one-shot generation.
89
+ Determines the entry point of the graph based on the initial state.
160
90
  """
161
- content_str = state["messages"][-1].content
162
- initial_input = json.loads(content_str)
91
+ if state.get("generated_agent"):
92
+ logger.info("Routing to: modify_agent.")
93
+ return "modify_agent"
94
+ else:
95
+ logger.info("Routing to: create_agent.")
96
+ return "create_agent"
163
97
 
164
- conversation_history = initial_input.get("conversation_history")
165
- tool_config = initial_input.get("tool_config")
98
+ async def _create_agent(self, state: BuilderState):
99
+ """Generates a new agent profile from scratch."""
100
+ if not state.get("user_task") and not state["messages"]:
101
+ raise ValueError("To create a new agent, provide either a 'user_task' or 'messages'.")
102
+
103
+ user_task = state.get("user_task") or "Not provided"
104
+ conversation_history = []
105
+
106
+ if state["messages"]:
107
+ content_str = state["messages"][-1].content
108
+ raw_history = json.loads(content_str)
109
+ conversation_history = _clean_conversation_history(raw_history)
110
+
111
+ prompt = NEW_AGENT_PROMPT.format(
112
+ user_task=user_task,
113
+ conversation_history=json.dumps(conversation_history, indent=2),
114
+ )
166
115
 
167
- if not conversation_history or not tool_config:
168
- raise ValueError("Input must be a dictionary containing 'conversation_history' and 'tool_config'.")
116
+ structured_llm = self.llm.with_structured_output(Agent)
117
+ generated_agent = await structured_llm.ainvoke(prompt)
118
+
119
+ logger.info(f"Successfully created new agent '{generated_agent.name}'.")
169
120
 
170
- prompt = AGENT_FROM_CONVERSATION_PROMPT.format(
121
+ return Command(
122
+ update={"generated_agent": generated_agent},
123
+ goto="create_or_update_tool_config",
124
+ )
125
+
126
+ async def _modify_agent(self, state: BuilderState):
127
+ """Modifies an existing agent based on new user feedback and/or conversation."""
128
+ existing_agent = state["generated_agent"]
129
+
130
+ if not state.get("user_task") and not state["messages"]:
131
+ raise ValueError("To modify an agent, provide either a 'user_task' or 'messages'.")
132
+
133
+ modification_request = state.get("user_task") or "No direct modification request provided."
134
+
135
+ conversation_history = []
136
+ if state["messages"]:
137
+ content_str = state["messages"][-1].content
138
+ raw_history = json.loads(content_str)
139
+ conversation_history = _clean_conversation_history(raw_history)
140
+
141
+ prompt = MODIFY_AGENT_PROMPT.format(
142
+ existing_instructions=existing_agent.instructions,
143
+ modification_request=modification_request,
171
144
  conversation_history=json.dumps(conversation_history, indent=2),
172
- tool_config=json.dumps(tool_config, indent=2),
173
145
  )
174
146
 
175
147
  structured_llm = self.llm.with_structured_output(Agent)
176
- generated_agent_profile = await structured_llm.ainvoke(prompt)
177
-
178
- yield {
179
- "generated_agent": generated_agent_profile,
180
- "tool_config": tool_config,
181
- "messages": [
182
- AIMessage(
183
- content=f"Successfully generated agent '{generated_agent_profile.name}' from the conversation history."
184
- )
185
- ],
186
- }
148
+ modified_agent = await structured_llm.ainvoke(prompt)
149
+
150
+ logger.info(f"Successfully modified agent '{modified_agent.name}'.")
151
+
152
+ return Command(
153
+ update={"generated_agent": modified_agent},
154
+ goto="create_or_update_tool_config"
155
+ )
156
+
157
+ async def _get_tool_config_for_task(self, task: str) -> ToolConfig:
158
+ """Helper method to find and configure tools for a given task string."""
159
+ if not task:
160
+ return {}
161
+ tool_finder_graph = build_tool_node_graph(self.llm, self.registry)
162
+ final_state = await tool_finder_graph.ainvoke({"original_task": task})
163
+ return final_state.get("execution_plan") or {}
164
+
165
+ async def _create_or_update_tool_config(self, state: BuilderState):
166
+ """
167
+ Creates or updates the tool configuration by synthesizing tools from multiple sources:
168
+ 1. Existing tool config (if any).
169
+ 2. Tools extracted from conversation history.
170
+ 3. Tools inferred from the agent's primary instructions.
171
+ 4. Tools inferred from the user's direct input/task.
172
+ """
173
+ # 1. Get the existing configuration, if it exists
174
+ final_tool_config = state.get("tool_config") or {}
175
+
176
+ # 2. Extract tools directly from the conversation history
177
+ if state["messages"]:
178
+ content_str = state["messages"][-1].content
179
+ raw_history = json.loads(content_str)
180
+ history_tool_config = _extract_tools_from_history(raw_history)
181
+ final_tool_config = _merge_tool_configs(final_tool_config, history_tool_config)
182
+
183
+ # 3. Find tools based on the agent's synthesized instructions (even if modifying)
184
+ instructions_task = state["generated_agent"].instructions
185
+ instructions_tool_config = await self._get_tool_config_for_task(instructions_task)
186
+ final_tool_config = _merge_tool_configs(final_tool_config, instructions_tool_config)
187
+
188
+ # 4. Find tools based on the direct user input (when creating a new agent)
189
+ user_task = state.get("user_task")
190
+ if user_task:
191
+ user_task_tool_config = await self._get_tool_config_for_task(user_task)
192
+ final_tool_config = _merge_tool_configs(final_tool_config, user_task_tool_config)
193
+
194
+ logger.info(f"Final synthesized tool configuration: {final_tool_config}")
195
+
196
+ return Command(
197
+ update={"tool_config": final_tool_config},
198
+ goto=END,
199
+ )
187
200
 
188
201
  async def _build_graph(self):
189
202
  """Builds the conversational agent graph."""
190
203
  builder = StateGraph(BuilderState)
191
204
 
192
- # Add nodes
193
- builder.add_node("prepare_for_build", self._prepare_for_build)
194
205
  builder.add_node("create_agent", self._create_agent)
195
- builder.add_node("create_tool_config", self._create_tool_config)
196
- builder.add_node("synthesize_new_task", self._synthesize_new_task_from_feedback)
197
- builder.add_node("synthesize_from_conversation", self._synthesize_from_conversation)
206
+ builder.add_node("modify_agent", self._modify_agent)
207
+ builder.add_node("create_or_update_tool_config", self._create_or_update_tool_config)
198
208
 
199
- # The conditional entry point decides the workflow
200
209
  builder.add_conditional_edges(
201
210
  START,
202
211
  self._entry_point_router,
203
- {
204
- "prepare_for_build": "prepare_for_build",
205
- "synthesize_new_task": "synthesize_new_task",
206
- "synthesize_from_conversation": "synthesize_from_conversation",
207
- },
208
212
  )
209
213
 
210
- # Path for a fresh interactive build
211
- builder.add_edge("prepare_for_build", "create_agent")
212
- builder.add_edge("prepare_for_build", "create_tool_config")
213
-
214
- # Path for modifying an existing build
215
- builder.add_edge("synthesize_new_task", "create_agent")
216
- builder.add_edge("synthesize_new_task", "create_tool_config")
217
-
218
- # Path for building from conversation ends after its single step
219
- builder.add_edge("synthesize_from_conversation", END)
220
-
221
- # Interactive creation nodes lead to the end of the run
222
- builder.add_edge("create_agent", END)
223
- builder.add_edge("create_tool_config", END)
224
-
225
- return builder.compile(checkpointer=self.memory)
214
+ return builder.compile(checkpointer=self.memory)
@@ -0,0 +1,73 @@
1
+ from collections import defaultdict
2
+ import collections
3
+
4
+ from loguru import logger
5
+ from universal_mcp.types import ToolConfig
6
+
7
+ def _extract_tools_from_history(history: list[dict]) -> ToolConfig:
8
+ """
9
+ Parses a conversation history to find and extract all tool names,
10
+ returning them in a structured ToolConfig format.
11
+
12
+ This function identifies messages with a "type" of "tool", extracts the
13
+ tool's name from the "name" key, and filters out a predefined list of
14
+ excluded tools. The remaining tool names are expected to be in an
15
+ "app_id__tool_id" format. These are then organized into a dictionary
16
+ mapping each app_id to a sorted list of its associated tool_ids.
17
+ """
18
+ apps_with_tools = collections.defaultdict(set)
19
+ excluded_tools = {"search_tools", "load_tools"}
20
+
21
+ for message in history:
22
+ if message.get("type") == "tool":
23
+ full_tool_name = message.get("name")
24
+ if not full_tool_name or full_tool_name in excluded_tools:
25
+ continue
26
+
27
+ if "__" in full_tool_name:
28
+ app_id, tool_id = full_tool_name.split("__", 1)
29
+ apps_with_tools[app_id].add(tool_id)
30
+
31
+ return {
32
+ app_id: sorted(list(tools))
33
+ for app_id, tools in apps_with_tools.items()
34
+ }
35
+
36
+
37
+ def _clean_conversation_history(history: list[dict]) -> list[dict]:
38
+ """
39
+ Filters a raw conversation history, keeping only messages relevant for
40
+ agent synthesis (human, ai, and tool messages with a name containing double underscores).
41
+ """
42
+ cleaned_history = []
43
+ for message in history:
44
+ msg_type = message.get("type")
45
+
46
+ if msg_type in ["human", "ai"]:
47
+ cleaned_history.append(message)
48
+ elif msg_type == "tool" and isinstance(message.get("name"), str) and "__" in message["name"]:
49
+ cleaned_history.append(message)
50
+
51
+ return cleaned_history
52
+
53
+
54
+ def _merge_tool_configs(old_config: ToolConfig, new_config: ToolConfig) -> ToolConfig:
55
+ """Merges two tool configurations, taking the union of tools for each app."""
56
+ if not old_config:
57
+ return new_config
58
+ if not new_config:
59
+ return old_config
60
+
61
+ # Start with a copy of the old configuration
62
+ merged_config = defaultdict(set)
63
+ for app, tools in old_config.items():
64
+ merged_config[app].update(tools)
65
+
66
+ # Add the new tools, ensuring uniqueness
67
+ for app, tools in new_config.items():
68
+ merged_config[app].update(tools)
69
+
70
+ # Convert the sets back to sorted lists for consistent output
71
+ final_config = {app: sorted(list(tool_set)) for app, tool_set in merged_config.items()}
72
+ logger.info(f"Merged tool configuration: {final_config}")
73
+ return final_config
@@ -1,173 +1,54 @@
1
- AGENT_BUILDER_INSTRUCTIONS = r"""
2
- You are a specialized Agent Generation AI, tasked with creating intelligent, effective, and context-aware AI agents based on user requests.
3
-
4
- When given a user's request, immediately follow this structured process:
5
-
6
- # 1. Intent Breakdown
7
- - Clearly identify the primary goal the user wants the agent to achieve.
8
- - Recognize any special requirements, constraints, formatting requests, or interaction rules.
9
- - Summarize your understanding briefly to ensure alignment with user intent.
1
+ NEW_AGENT_PROMPT = r"""
2
+ # ROLE & GOAL
3
+ You are a specialized Agent Generation AI. Your primary function is to create a complete, high-quality AI agent profile based on the information provided.
10
4
 
11
- # 2. Agent Profile Definition
12
- - **Name (2-4 words)**: Concise, clear, and memorable name reflecting core functionality.
13
- - **Description (1-2 sentences)**: Captures the unique value and primary benefit to users.
14
- - **Expertise**: Precise domain-specific expertise area. Avoid vague or overly general titles.
15
- - **Instructions**: Compose detailed, highly actionable system instructions that directly command the agent's behavior. Respond in markdown as this text will be rendered in a rich text editor. Write instructions as clear imperatives, without preamble, assuming the agent identity is already established externally.
16
- - **Schedule**: If the user specifies a schedule, you should also provide a cron expression for the agent to run on. The schedule should be in a proper cron expression and nothing more. Do not respond with any other information or explain your reasoning for the schedule, otherwise this will cause a parsing error that is undesirable.
5
+ # INPUTS
6
+ 1. **User Task (Optional):** A brief, initial request from the user. This might be vague or specific.
7
+ 2. **Conversation History (Optional):** A transcript of a conversation. This is the **primary source of truth**. If the conversation history is provided, it should be prioritized over the User Task to understand the user's full, potentially multi-step, objective.
17
8
 
18
- ## ROLE & RESPONSIBILITY
19
- - Clearly state the agent's primary mission, e.g., "Your primary mission is...", "Your core responsibility is...".
20
- - Outline the exact tasks it handles, specifying expected input/output clearly.
9
+ # INSTRUCTIONS
10
+ Analyze the available inputs to fully understand the user's intent. Synthesize this understanding into a complete agent profile according to the specified JSON schema.
21
11
 
22
- ## INTERACTION STYLE
23
- - Define exactly how to communicate with users: tone, format, response structure.
24
- - Include explicit commands, e.g., "Always wrap responses in \`\`\`text\`\`\` blocks.", "Never add greetings or meta-information.", "Always provide outputs in user's requested languages."
12
+ - The first line of the `instructions` field in your output MUST be a single, complete sentence that serves as the definitive task for the agent. This sentence should be synthesized from your analysis of the inputs.
13
+ - The rest of the `instructions` should provide clear, actionable commands for the agent, covering its role, responsibilities, interaction style, and output formatting.
25
14
 
26
- ## OUTPUT FORMATTING RULES
27
- - Clearly specify formatting standards required by the user (e.g., JSON, plain text, markdown).
28
- - Include explicit examples to illustrate correct formatting.
15
+ # TASK
16
+ Based on the following inputs, generate a complete agent profile.
29
17
 
30
- ## LIMITATIONS & CONSTRAINTS
31
- - Explicitly define boundaries of the agent's capabilities.
32
- - Clearly state what the agent must never do or say.
33
- - Include exact phrases for declining requests outside scope.
18
+ **User Task:**
19
+ {user_task}
34
20
 
35
- ## REAL-WORLD EXAMPLES
36
- Provide two explicit interaction examples showing:
37
- - User's typical request.
38
- - Final agent response demonstrating perfect compliance.
21
+ **Conversation History:**
22
+ {conversation_history}
39
23
 
40
- Create an agent that feels thoughtfully designed, intelligent, and professionally reliable, perfectly matched to the user's original intent.
24
+ **YOUR JSON OUTPUT:**
41
25
  """
42
26
 
43
-
44
- TASK_SYNTHESIS_PROMPT = r"""
27
+ MODIFY_AGENT_PROMPT = r"""
45
28
  # ROLE & GOAL
46
- You are a 'Task Synthesizer' AI. Your sole purpose is to combine an original user task and a subsequent modification request into a single, complete, and coherent new task. This new task must be a standalone instruction that accurately reflects the user's final intent and can be used to configure a new AI agent from scratch.
29
+ You are an expert Agent Modification AI. Your task is to intelligently update an existing AI agent's profile by integrating a user's modification request.
47
30
 
48
31
  # CORE PRINCIPLES
49
- 1. **Preserve All Details:** You must retain all specific, unmodified details from the original task (e.g., email addresses, subjects, search queries, file names).
50
- 2. **Seamless Integration:** The user's modification must be integrated perfectly into the original task's context, replacing or adding information as required.
51
- 3. **Clarity and Directness:** The final task should be a direct command, phrased as if it were the user's very first request.
52
- 4. **Strict Output Format:** Your output MUST BE ONLY the new synthesized task string. Do not include any preamble, explanation, or quotation marks.
53
-
54
- ---
55
- # EXAMPLES
56
-
57
- **EXAMPLE 1: Changing the application for an email task**
58
-
59
- **Original Task:**
60
- "Send an email to manoj@agentr.dev with the subject 'Hello' and body 'This is a test of the Gmail agent.' from my Gmail account"
61
-
62
- **Modification Request:**
63
- "Please use my Outlook account for this instead of Gmail."
64
-
65
- **New Synthesized Task:**
66
- Send an email to manoj@agentr.dev with the subject 'Hello' and body 'This is a test of the Outlook agent.' from my Outlook account
67
-
68
- ---
69
- **EXAMPLE 2: Modifying the scope and source for a calendar task**
70
-
71
- **Original Task:**
72
- "Show me events from today's Google Calendar"
73
-
74
- **Modification Request:**
75
- "Actually, I need to see the whole week, not just today. And can you check my Microsoft 365 calendar?"
76
-
77
- **New Synthesized Task:**
78
- Show me events for the whole week from my Microsoft 365 calendar
79
-
80
- ---
81
- **EXAMPLE 3: Changing the target and tool for a web search task**
82
-
83
- **Original Task:**
84
- "Find the best restaurants in Goa using exa web search"
85
-
86
- **Modification Request:**
87
- "Could you look for hotels instead of restaurants, and please use Perplexity for it."
88
-
89
- **New Synthesized Task:**
90
- Find the best hotels in Goa using Perplexity.
91
-
92
- ---
93
- **EXAMPLE 4: Altering the final action of a multi-step task**
32
+ 1. **Synthesize, Don't Just Add:** Do not simply append the modification request. You must seamlessly integrate the user's feedback into the agent's existing instructions, creating a new, coherent set of commands.
33
+ 2. **Holistic Update:** The modification may require changes to not only the instructions but also the agent's name, description, and expertise. You must re-evaluate the entire profile.
34
+ 3. **Prioritize New Information:** The user's latest modification request and any new conversation history are the primary drivers for the changes.
94
35
 
95
- **Original Task:**
96
- "search reddit for posts on elon musk and then post a meme on him on linkedin"
97
-
98
- **Modification Request:**
99
- "Let's not post anything. Just find the posts and then summarize the key points into a text file for me."
100
-
101
- **New Synthesized Task:**
102
- search reddit for posts on elon musk and then summarize the key points into a text file
103
-
104
- ---
105
- # YOUR TASK
36
+ # INPUTS
37
+ 1. **Existing Agent Instructions:** The original instructions that define the agent's current behavior.
38
+ 2. **Modification Request:** The user's new input, specifying the desired changes.
39
+ 3. **Conversation History (Optional):** A transcript of the conversation that may provide additional context for the modification.
106
40
 
107
- Now, perform this synthesis for the following inputs.
41
+ # TASK
42
+ Update the agent profile based on the user's feedback.
108
43
 
109
- **Original Task:**
110
- {original_task}
44
+ **Existing Agent Instructions:**
45
+ {existing_instructions}
111
46
 
112
47
  **Modification Request:**
113
48
  {modification_request}
114
49
 
115
- **New Synthesized Task:**
116
- """
117
-
118
- AGENT_FROM_CONVERSATION_PROMPT = r"""
119
- # ROLE & GOAL
120
- You are a highly intelligent 'Agent Analyst' AI. Your sole purpose is to analyze a raw conversation transcript between a user and an AI assistant and a definitive list of tools the assistant used. From this data, you must synthesize a complete, reusable AI agent profile.
121
-
122
- # INPUTS
123
- 1. **Conversation History:** A transcript of the dialogue.
124
- 2. **Used Tools:** A definitive list of tool configurations (`{{app_id: [tool_names]}}`) that were successfully used to fulfill the user's requests in the conversation.
125
-
126
- # 1. Intent Breakdown
127
- - Clearly identify the primary goal the user wants the agent to achieve.
128
- - Recognize any special requirements, constraints, formatting requests, or interaction rules.
129
- - Summarize your understanding briefly to ensure alignment with user intent.
130
-
131
- # 2. Agent Profile Definition
132
- - **Name (2-4 words)**: Concise, clear, and memorable name reflecting core functionality.
133
- - **Description (1-2 sentences)**: Captures the unique value and primary benefit to users.
134
- - **Expertise**: Precise domain-specific expertise area. Avoid vague or overly general titles.
135
- - **Instructions**: Compose detailed, highly actionable system instructions that directly command the agent's behavior. Respond in markdown as this text will be rendered in a rich text editor. Write instructions as clear imperatives, without preamble, assuming the agent identity is already established externally.
136
- - **Schedule**: If the user specifies a schedule, you should also provide a cron expression for the agent to run on. The schedule should be in a proper cron expression and nothing more. Do not respond with any other information or explain your reasoning for the schedule, otherwise this will cause a parsing error that is undesirable.
137
-
138
- ## ROLE & RESPONSIBILITY
139
- - Clearly state the agent's primary mission, e.g., "Your primary mission is...", "Your core responsibility is...".
140
- - Outline the exact tasks it handles, specifying expected input/output clearly.
141
-
142
- ## INTERACTION STYLE
143
- - Define exactly how to communicate with users: tone, format, response structure.
144
- - Include explicit commands, e.g., "Always wrap responses in \`\`\`text\`\`\` blocks.", "Never add greetings or meta-information.", "Always provide outputs in user's requested languages."
145
-
146
- ## OUTPUT FORMATTING RULES
147
- - Clearly specify formatting standards required by the user (e.g., JSON, plain text, markdown).
148
- - Include explicit examples to illustrate correct formatting.
149
-
150
- ## LIMITATIONS & CONSTRAINTS
151
- - Explicitly define boundaries of the agent's capabilities.
152
- - Clearly state what the agent must never do or say.
153
- - Include exact phrases for declining requests outside scope.
154
-
155
- ## REAL-WORLD EXAMPLES
156
- Provide two explicit interaction examples showing:
157
- - User's typical request.
158
- - Final agent response demonstrating perfect compliance.
159
-
160
- Create an agent that feels thoughtfully designed, intelligent, and professionally reliable, perfectly matched to the user's original intent.
161
-
162
- # YOUR TASK
163
-
164
- Now, perform this analysis for the following inputs.
165
-
166
- **INPUT - Conversation History:**
50
+ **Conversation History:**
167
51
  {conversation_history}
168
52
 
169
- **INPUT - Used Tools:**
170
- {tool_config}
171
-
172
- **YOUR JSON OUTPUT:**
173
- """
53
+ **YOUR UPDATED JSON OUTPUT:**
54
+ """
@@ -21,4 +21,4 @@ class BuilderState(TypedDict):
21
21
  user_task: str | None
22
22
  generated_agent: Agent | None
23
23
  tool_config: ToolConfig | None
24
- messages: Annotated[Sequence[BaseMessage], add_messages]
24
+ messages: Annotated[Sequence[BaseMessage], add_messages]