mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-29 15:59:42 +02:00
determine the content to be saved to the experience pool using cmd_prompt_exp_part.
This commit is contained in:
parent
39360b41c5
commit
bf21bbf12e
13 changed files with 113 additions and 139 deletions
|
|
@ -1,5 +1,6 @@
|
|||
"""Action Node context builder."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from metagpt.exp_pool.context_builders.base import BaseContextBuilder
|
||||
|
||||
|
|
@ -17,17 +18,12 @@ Consider **Experiences** to generate a better answer.
|
|||
|
||||
|
||||
class ActionNodeContextBuilder(BaseContextBuilder):
|
||||
async def build(self, **kwargs) -> str:
|
||||
async def build(self, req: Any) -> str:
|
||||
"""Builds the action node context string.
|
||||
|
||||
Args:
|
||||
**kwargs: Arbitrary keyword arguments, expecting 'req' as a key.
|
||||
|
||||
Returns:
|
||||
str: The formatted context string using the request and formatted experiences.
|
||||
If no experiences are available, returns the request as is.
|
||||
If there are no experiences, returns the original `req`;
|
||||
otherwise returns context with `req` and formatted experiences.
|
||||
"""
|
||||
req = kwargs.get("req", "")
|
||||
exps = self.format_exps()
|
||||
|
||||
return ACTION_NODE_CONTEXT_TEMPLATE.format(req=req, exps=exps) if exps else req
|
||||
|
|
|
|||
|
|
@ -16,8 +16,11 @@ class BaseContextBuilder(BaseModel, ABC):
|
|||
exps: list[Experience] = []
|
||||
|
||||
@abstractmethod
|
||||
async def build(self, **kwargs) -> Any:
|
||||
"""Build context from parameters."""
|
||||
async def build(self, req: Any) -> Any:
|
||||
"""Build context from req.
|
||||
|
||||
Do not modify `req`. If modification is necessary, use copy.deepcopy to create a copy first.
|
||||
"""
|
||||
|
||||
def format_exps(self) -> str:
|
||||
"""Format experiences into a numbered list of strings.
|
||||
|
|
|
|||
|
|
@ -1,34 +1,36 @@
|
|||
"""RoleZero context builder."""
|
||||
|
||||
import copy
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from metagpt.exp_pool.context_builders.base import BaseContextBuilder
|
||||
|
||||
|
||||
class RoleZeroContextBuilder(BaseContextBuilder):
|
||||
async def build(self, **kwargs) -> list[dict]:
|
||||
"""Builds the context by updating the req with formatted experiences.
|
||||
async def build(self, req: Any) -> list[dict]:
|
||||
"""Builds the role zero context string.
|
||||
|
||||
Args:
|
||||
**kwargs: Arbitrary keyword arguments, expecting 'req' as a key.
|
||||
|
||||
Returns:
|
||||
list[dict]: The updated request with formatted experiences or the original request if no experiences are available.
|
||||
Note:
|
||||
1. The expected format for `req`, e.g., [{...}, {"role": "user", "content": "context"}, {"role": "user", "content": "context exp part"}].
|
||||
2. Returns the original `req` if it is empty, incorrectly formatted or there are no experiences.
|
||||
3. Creates a copy of req and replaces the example content in the copied req with actual experiences.
|
||||
"""
|
||||
req = kwargs.get("req", [])
|
||||
if not req:
|
||||
if not req or len(req) < 2:
|
||||
return req
|
||||
|
||||
exps = self.format_exps()
|
||||
if not exps:
|
||||
return req
|
||||
|
||||
req[-1]["content"] = self.replace_example_content(req[-1].get("content", ""), exps)
|
||||
req_copy = copy.deepcopy(req)
|
||||
|
||||
return req
|
||||
req_copy[-2]["content"] = self.replace_example_content(req_copy[-2].get("content", ""), exps)
|
||||
|
||||
return req_copy
|
||||
|
||||
def replace_example_content(self, text: str, new_example_content: str) -> str:
|
||||
return self.replace_content_between_markers(text, "# Example", "# Instruction", new_example_content)
|
||||
return self.replace_content_between_markers(text, "# Example", "# Available Commands", new_example_content)
|
||||
|
||||
@staticmethod
|
||||
def replace_content_between_markers(text: str, start_marker: str, end_marker: str, new_content: str) -> str:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"""Simple context builder."""
|
||||
|
||||
|
||||
from typing import Any
|
||||
|
||||
from metagpt.exp_pool.context_builders.base import BaseContextBuilder
|
||||
|
||||
SIMPLE_CONTEXT_TEMPLATE = """
|
||||
|
|
@ -20,5 +22,5 @@ Consider **Experiences** to generate a better answer.
|
|||
|
||||
|
||||
class SimpleContextBuilder(BaseContextBuilder):
|
||||
async def build(self, **kwargs) -> str:
|
||||
return SIMPLE_CONTEXT_TEMPLATE.format(req=kwargs.get("req", ""), exps=self.format_exps())
|
||||
async def build(self, req: Any) -> str:
|
||||
return SIMPLE_CONTEXT_TEMPLATE.format(req=req, exps=self.format_exps())
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ class ExpCacheHandler(BaseModel):
|
|||
async def _build_context(self) -> str:
|
||||
self.context_builder.exps = self._exps
|
||||
|
||||
return await self.context_builder.build(**self.kwargs)
|
||||
return await self.context_builder.build(self.kwargs["req"])
|
||||
|
||||
async def _execute_function(self):
|
||||
self.kwargs["req"] = await self._build_context()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
import copy
|
||||
import json
|
||||
|
||||
from metagpt.exp_pool.context_builders import RoleZeroContextBuilder
|
||||
from metagpt.exp_pool.serializers.simple import SimpleSerializer
|
||||
|
||||
|
||||
|
|
@ -11,14 +10,15 @@ class RoleZeroSerializer(SimpleSerializer):
|
|||
def serialize_req(self, req: list[dict]) -> str:
|
||||
"""Serialize the request for database storage, ensuring it is a string.
|
||||
|
||||
This function does not modify `req`; it only extracts the necessary content from `req` because `req` may be very lengthy and could cause embedding errors.
|
||||
Only extracts the necessary content from `req` because `req` may be very lengthy and could cause embedding errors.
|
||||
|
||||
Args:
|
||||
req (list[dict]): The request to be serialized. Example:
|
||||
[
|
||||
{"role": "user", "content": "..."},
|
||||
{"role": "assistant", "content": "..."},
|
||||
{"role": "user", "content": "..."},
|
||||
{"role": "user", "content": "context"},
|
||||
{"role": "user", "content": "context exp part"},
|
||||
]
|
||||
|
||||
Returns:
|
||||
|
|
@ -28,12 +28,12 @@ class RoleZeroSerializer(SimpleSerializer):
|
|||
return ""
|
||||
|
||||
filtered_req = self._filter_req(req)
|
||||
self._clean_last_entry_content(filtered_req)
|
||||
filtered_req.append(req[-1])
|
||||
|
||||
return json.dumps(filtered_req)
|
||||
|
||||
def _filter_req(self, req: list[dict]) -> list[dict]:
|
||||
"""Filter the request to include only necessary items and the last entry.
|
||||
"""Filter the `req` to include only necessary items.
|
||||
|
||||
Args:
|
||||
req (list[dict]): The original request.
|
||||
|
|
@ -45,20 +45,5 @@ class RoleZeroSerializer(SimpleSerializer):
|
|||
filtered_req = [
|
||||
copy.deepcopy(item) for item in req if "Command Editor.read executed: file_path" in item["content"]
|
||||
]
|
||||
filtered_req.append(copy.deepcopy(req[-1]))
|
||||
|
||||
return filtered_req
|
||||
|
||||
def _clean_last_entry_content(self, req: list[dict]):
|
||||
"""Modifies the content of the last element in the request to remove unnecessary sections, making the request more concise."""
|
||||
|
||||
last_content = req[-1]["content"]
|
||||
|
||||
last_content = RoleZeroContextBuilder.replace_content_between_markers(
|
||||
last_content, "# Data Structure", "# Current Plan", ""
|
||||
)
|
||||
last_content = RoleZeroContextBuilder.replace_content_between_markers(
|
||||
last_content, "# Example", "# Instruction", ""
|
||||
)
|
||||
|
||||
req[-1]["content"] = last_content
|
||||
|
|
|
|||
|
|
@ -8,7 +8,16 @@ Note:
|
|||
2. Carefully review your progress at the current task, if your actions so far has not fulfilled the task instruction, you should continue with current task. Otherwise, finish current task.
|
||||
3. Each time you finish a task, use RoleZero.reply_to_human to report your progress.
|
||||
"""
|
||||
CMD_PROMPT_EXP_PART = """
|
||||
# Current Plan
|
||||
{plan_status}
|
||||
|
||||
# Current Task
|
||||
{current_task}
|
||||
|
||||
# Instruction
|
||||
{instruction}
|
||||
"""
|
||||
CMD_PROMPT = """
|
||||
# Data Structure
|
||||
class Task(BaseModel):
|
||||
|
|
@ -18,21 +27,14 @@ class Task(BaseModel):
|
|||
task_type: str = ""
|
||||
assignee: str = ""
|
||||
|
||||
# Example
|
||||
{example}
|
||||
|
||||
# Available Commands
|
||||
{available_commands}
|
||||
Special Command: Use {{"command_name": "end"}} to do nothing or indicate completion of all requirements and the end of actions.
|
||||
|
||||
# Current Plan
|
||||
{plan_status}
|
||||
|
||||
# Current Task
|
||||
{current_task}
|
||||
|
||||
# Example
|
||||
{example}
|
||||
|
||||
# Instruction
|
||||
{instruction}
|
||||
{cmd_prompt_exp_part}
|
||||
|
||||
Pay close attention to the Example provided, you can reuse the example for your current situation if it fits.
|
||||
You may use any of the available commands to create a plan or update the plan. You may output mutiple commands, they will be executed sequentially.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from metagpt.exp_pool.serializers import RoleZeroSerializer
|
|||
from metagpt.logs import logger
|
||||
from metagpt.prompts.di.role_zero import (
|
||||
CMD_PROMPT,
|
||||
CMD_PROMPT_EXP_PART,
|
||||
JSON_REPAIR_PROMPT,
|
||||
ROLE_INSTRUCTION,
|
||||
)
|
||||
|
|
@ -144,13 +145,14 @@ class RoleZero(Role):
|
|||
tool_info = json.dumps({tool.name: tool.schemas for tool in tools})
|
||||
|
||||
### Make Decision Dynamically ###
|
||||
prompt = self.cmd_prompt.format(
|
||||
cmd_prompt_exp_part = CMD_PROMPT_EXP_PART.format(
|
||||
plan_status=plan_status,
|
||||
current_task=current_task,
|
||||
example=example,
|
||||
available_commands=tool_info,
|
||||
instruction=self.instruction.strip(),
|
||||
)
|
||||
prompt = self.cmd_prompt.format(
|
||||
example=example, available_commands=tool_info, cmd_prompt_exp_part=cmd_prompt_exp_part
|
||||
)
|
||||
memory = self.rc.memory.get(self.memory_k)
|
||||
if not self.browser.is_empty_page:
|
||||
pattern = re.compile(r"Command Browser\.(\w+) executed")
|
||||
|
|
@ -158,16 +160,24 @@ class RoleZero(Role):
|
|||
if pattern.match(msg.content):
|
||||
memory.insert(index, UserMessage(cause_by="browser", content=await self.browser.view()))
|
||||
break
|
||||
context = self.llm.format_msg(memory + [UserMessage(content=prompt)])
|
||||
# print(*context, sep="\n" + "*" * 5 + "\n")
|
||||
req = self.llm.format_msg(memory + [UserMessage(content=prompt), UserMessage(content=cmd_prompt_exp_part)])
|
||||
async with ThoughtReporter(enable_llm_stream=True):
|
||||
self.command_rsp = await self.llm_cached_aask(req=context, system_msgs=self.system_msg)
|
||||
self.command_rsp = await self.llm_cached_aask(req=req, system_msgs=self.system_msg)
|
||||
self.rc.memory.add(AIMessage(content=self.command_rsp))
|
||||
|
||||
return True
|
||||
|
||||
@exp_cache(context_builder=RoleZeroContextBuilder(), serializer=RoleZeroSerializer())
|
||||
async def llm_cached_aask(self, *, req: list[dict], system_msgs: list[str]) -> str:
|
||||
"""Use `exp_cache` to automatically manage experiences.
|
||||
|
||||
The `RoleZeroContextBuilder` attempts to add experiences to `req`.
|
||||
The `RoleZeroSerializer` extracts essential parts of `req` for the experience pool, trimming lengthy entries to retain only necessary parts.
|
||||
"""
|
||||
# Remove the "cmd_prompt_exp_part", it is only used within the exp_cache decorator.
|
||||
if req:
|
||||
req.pop()
|
||||
|
||||
return await self.llm.aask(req, system_msgs=system_msgs)
|
||||
|
||||
async def _act(self) -> Message:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue