mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Align ModeProvider tool names and instructions (#6071)
* Align ModeProvider tool names and instructions * Address PR comments
This commit is contained in:
committed by
GitHub
Unverified
parent
b2e77067e9
commit
bd4fc64b4d
@@ -14,14 +14,19 @@ from .._types import Message
|
||||
DEFAULT_MODE_SOURCE_ID = "agent_mode"
|
||||
DEFAULT_MODE_INSTRUCTIONS = (
|
||||
"## Agent Mode\n\n"
|
||||
"You can operate in different modes. Depending on the mode you are in, "
|
||||
"you will be required to follow different processes.\n\n"
|
||||
"Use the get_mode tool to check your current operating mode.\n"
|
||||
"Use the set_mode tool to switch between modes as your work progresses. "
|
||||
"Only use set_mode if the user explicitly instructs/allows you to change modes.\n\n"
|
||||
"- You can operate in different modes. Depending on the mode you are in, "
|
||||
"you will be required to follow different processes.\n"
|
||||
"- You must check the current mode after any user input, since the user may have changed the mode themselves, "
|
||||
"e.g. the user may have switched to 'plan' mode after a previous research task finished in 'execute' mode, "
|
||||
"meaning they want to review a plan first before execution.\n\n"
|
||||
"Use the mode_get tool to check your current operating mode.\n"
|
||||
"Use the mode_set tool to switch between modes as your work progresses. "
|
||||
"Only use mode_set if the user explicitly instructs/allows you to change modes.\n\n"
|
||||
"You are currently operating in the {current_mode} mode.\n\n"
|
||||
"### Mandatory Mode based Workflow\n\n"
|
||||
"For every new substantive user request, including short factual questions, "
|
||||
"your behavior is determined by the mode you are in.\n\n"
|
||||
"{available_modes}\n"
|
||||
"\n"
|
||||
"You are currently operating in the {current_mode} mode.\n"
|
||||
)
|
||||
DEFAULT_MODE_CHANGE_NOTIFICATION = (
|
||||
'[Mode changed: The operating mode has been switched from "{previous_mode}" to "{current_mode}". '
|
||||
@@ -31,13 +36,37 @@ DEFAULT_MODE_DESCRIPTIONS: dict[str, str] = {
|
||||
"plan": (
|
||||
"Use this mode when analyzing requirements, breaking down tasks, and creating plans. "
|
||||
"This is the interactive mode — ask clarifying questions, discuss options, and get user approval before "
|
||||
"proceeding."
|
||||
"proceeding.\n\n"
|
||||
"Process to follow when in plan mode:\n"
|
||||
"1. Analyze the request with the purpose of building a research plan.\n"
|
||||
"2. Create a list of todo items.\n"
|
||||
"3. If needed, use the provided tools to do some exploratory checks to help build a plan and determine "
|
||||
"what clarifying questions you may need from the user.\n"
|
||||
"4. Ask for clarifications from the user where needed.\n"
|
||||
" 1. Ask each clarification one by one.\n"
|
||||
" 2. When asking for clarification and you have specific options in mind, present them to the user, "
|
||||
"so they can choose the option instead of having to retype the entire response.\n"
|
||||
" 3. Do not proceed until you have received all the needed clarifications.\n"
|
||||
" 4. Do short exploratory research if it helps with being able to ask sensible clarifications from "
|
||||
"the user.\n"
|
||||
"5. Write the plan to a memory file, so that it is retained even if compaction happens. "
|
||||
"Make sure to update the plan file if the user requests changes.\n"
|
||||
"6. Present the plan to the user and ask for approval to switch to execute mode and process the plan.\n"
|
||||
"7. When approval is granted, always switch to execute mode (using the `mode_set` tool), "
|
||||
"and follow the steps for *Execute mode*."
|
||||
),
|
||||
"execute": (
|
||||
"Use this mode when carrying out approved plans. Work autonomously using your best judgement — do not ask "
|
||||
"the user questions or wait for feedback. Make reasonable decisions on your own so that there is a complete, "
|
||||
"useful result when the user returns. If you encounter ambiguity, choose the most reasonable option and note "
|
||||
"your choice."
|
||||
"Use this mode when carrying out approved plans. Work autonomously using your best judgment — do not ask "
|
||||
"the user questions or wait for feedback.\n\n"
|
||||
"Process to follow when in execute mode:\n"
|
||||
"1. If you don't have a plan or tasks yet, analyze the user request and create tasks and a plan. "
|
||||
"(**Skip this step if you came from plan mode**)\n"
|
||||
"2. Work autonomously — use your best judgment to make decisions and keep progressing without asking "
|
||||
"the user questions. The goal is to have a complete, useful result ready when the user returns.\n"
|
||||
"3. If you encounter ambiguity or an unexpected situation during execution, choose the most reasonable "
|
||||
"option, note your choice, and keep going.\n"
|
||||
"4. Mark tasks as completed as you finish them.\n"
|
||||
"5. Continue working, thinking and calling tools until you have the research result for the user."
|
||||
),
|
||||
}
|
||||
|
||||
@@ -179,8 +208,8 @@ class AgentModeProvider(ContextProvider):
|
||||
``"plan"`` (interactive planning) and ``"execute"`` (autonomous execution).
|
||||
|
||||
This provider exposes the following tools to the agent:
|
||||
- ``set_mode``: Switch the agent's operating mode.
|
||||
- ``get_mode``: Retrieve the agent's current operating mode.
|
||||
- ``mode_set``: Switch the agent's operating mode.
|
||||
- ``mode_get``: Retrieve the agent's current operating mode.
|
||||
|
||||
Public helper functions ``get_agent_mode`` and ``set_agent_mode`` allow external code to programmatically read
|
||||
and change the mode.
|
||||
@@ -223,7 +252,7 @@ class AgentModeProvider(ContextProvider):
|
||||
def _build_instructions(self, current_mode: str) -> str:
|
||||
"""Build the mode guidance injected for the current session."""
|
||||
mode_lines = "".join(
|
||||
f'- "{self._mode_display_names[mode]}": {description}\n'
|
||||
f"#### {self._mode_display_names[mode]}\n\n{description}\n\n"
|
||||
for mode, description in self.mode_descriptions.items()
|
||||
)
|
||||
instructions = self.instructions or DEFAULT_MODE_INSTRUCTIONS
|
||||
@@ -257,8 +286,8 @@ class AgentModeProvider(ContextProvider):
|
||||
provider_state = _get_mode_state(session, source_id=self.source_id)
|
||||
previous_mode = provider_state.pop(_PREVIOUS_MODE_STATE_KEY, None)
|
||||
|
||||
@tool(name="set_mode", approval_mode="never_require")
|
||||
def set_mode(mode: str) -> str:
|
||||
@tool(name="mode_set", approval_mode="never_require")
|
||||
def mode_set(mode: str) -> str:
|
||||
"""Switch the agent's operating mode."""
|
||||
# The agent invoked the tool itself, so it knows the mode just changed — bypass
|
||||
# ``set_agent_mode`` to avoid triggering a notification message on the next turn.
|
||||
@@ -267,8 +296,8 @@ class AgentModeProvider(ContextProvider):
|
||||
tool_state["current_mode"] = normalized_mode
|
||||
return json.dumps({"mode": normalized_mode, "message": f"Mode changed to '{normalized_mode}'."})
|
||||
|
||||
@tool(name="get_mode", approval_mode="never_require")
|
||||
def get_mode() -> str:
|
||||
@tool(name="mode_get", approval_mode="never_require")
|
||||
def mode_get() -> str:
|
||||
"""Get the agent's current operating mode."""
|
||||
current_mode_value = get_agent_mode(
|
||||
session,
|
||||
@@ -282,11 +311,11 @@ class AgentModeProvider(ContextProvider):
|
||||
self.source_id,
|
||||
[self._build_instructions(current_mode)],
|
||||
)
|
||||
context.extend_tools(self.source_id, [set_mode, get_mode])
|
||||
context.extend_tools(self.source_id, [mode_set, mode_get])
|
||||
if isinstance(previous_mode, str) and previous_mode != current_mode:
|
||||
# Inject a user-role message announcing the external mode change. System instructions
|
||||
# always render first in the chat history, so the agent can otherwise stay anchored to
|
||||
# the most recent ``set_mode`` tool call rather than the new mode.
|
||||
# the most recent ``mode_set`` tool call rather than the new mode.
|
||||
previous_display = self._mode_display_names.get(previous_mode, previous_mode)
|
||||
current_display = self._mode_display_names.get(current_mode, current_mode)
|
||||
notification = DEFAULT_MODE_CHANGE_NOTIFICATION.format(
|
||||
|
||||
@@ -95,8 +95,10 @@ async def test_agent_mode_context_provider_normalizes_custom_modes(
|
||||
)
|
||||
instructions = options["instructions"]
|
||||
assert isinstance(instructions, str)
|
||||
assert '"Draft": Draft it.' in instructions
|
||||
assert '"Final": Finalize it.' in instructions
|
||||
assert "#### Draft" in instructions
|
||||
assert "Draft it." in instructions
|
||||
assert "#### Final" in instructions
|
||||
assert "Finalize it." in instructions
|
||||
assert "You are currently operating in the draft mode." in instructions
|
||||
|
||||
assert (
|
||||
@@ -125,8 +127,8 @@ async def test_agent_mode_context_provider_serializes_tool_outputs_as_json(
|
||||
)
|
||||
tools = options["tools"]
|
||||
assert isinstance(tools, list)
|
||||
get_mode_tool = _tool_by_name(tools, "get_mode")
|
||||
set_mode_tool = _tool_by_name(tools, "set_mode")
|
||||
get_mode_tool = _tool_by_name(tools, "mode_get")
|
||||
set_mode_tool = _tool_by_name(tools, "mode_set")
|
||||
|
||||
initial_mode = await get_mode_tool.invoke()
|
||||
assert json.loads(initial_mode[0].text) == {"mode": mode_name}
|
||||
@@ -152,13 +154,13 @@ async def test_agent_mode_context_provider_updates_agent_mode(
|
||||
instructions = options["instructions"]
|
||||
assert isinstance(instructions, str)
|
||||
assert "## Agent Mode" in instructions
|
||||
assert "Use the set_mode tool to switch between modes as your work progresses." in instructions
|
||||
assert "Use the mode_set tool to switch between modes as your work progresses." in instructions
|
||||
assert "ask clarifying questions, discuss options, and get user approval before proceeding" in instructions
|
||||
assert "If you encounter ambiguity, choose the most reasonable option and note your choice" in instructions
|
||||
assert "If you encounter ambiguity" in instructions
|
||||
assert "You are currently operating in the plan mode." in instructions
|
||||
|
||||
get_mode_tool = _tool_by_name(tools, "get_mode")
|
||||
set_mode_tool = _tool_by_name(tools, "set_mode")
|
||||
get_mode_tool = _tool_by_name(tools, "mode_get")
|
||||
set_mode_tool = _tool_by_name(tools, "mode_set")
|
||||
|
||||
initial_mode = await get_mode_tool.invoke()
|
||||
assert json.loads(initial_mode[0].text) == {"mode": "plan"}
|
||||
@@ -218,13 +220,13 @@ async def test_agent_mode_provider_injects_user_message_after_external_change(
|
||||
provider = AgentModeProvider()
|
||||
agent = Agent(client=chat_client_base, context_providers=[provider])
|
||||
|
||||
# First run: agent uses set_mode tool to switch to execute. The tool path must NOT queue a
|
||||
# First run: agent uses mode_set tool to switch to execute. The tool path must NOT queue a
|
||||
# notification because the agent already saw its own tool call in the chat history.
|
||||
_, first_options = await agent._prepare_session_and_messages( # type: ignore[reportPrivateUsage]
|
||||
session=session,
|
||||
input_messages=[Message(role="user", contents=["Plan first."])],
|
||||
)
|
||||
set_mode_tool = _tool_by_name(first_options["tools"], "set_mode")
|
||||
set_mode_tool = _tool_by_name(first_options["tools"], "mode_set")
|
||||
await set_mode_tool.invoke(arguments={"mode": "execute"})
|
||||
assert "previous_mode_for_notification" not in session.state[provider.source_id]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user