mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-10-08 03:10:24 +00:00
add simple summarizer
This commit is contained in:
parent
5ec394bccb
commit
15da7cc730
13 changed files with 212 additions and 30 deletions
|
|
@ -7,8 +7,6 @@ from pydantic import BaseModel, Field
|
|||
from experiencemaker.schema.vector_store_node import VectorStoreNode
|
||||
from experiencemaker.utils.registry import Registry
|
||||
|
||||
EMBEDDING_MODEL_REGISTRY = Registry("embedding_model")
|
||||
|
||||
|
||||
class BaseEmbeddingModel(BaseModel, ABC):
|
||||
model_name: str = Field(default=..., description="model name")
|
||||
|
|
@ -87,3 +85,6 @@ class BaseEmbeddingModel(BaseModel, ABC):
|
|||
|
||||
else:
|
||||
raise RuntimeError(f"unsupported type={type(nodes)}")
|
||||
|
||||
|
||||
EMBEDDING_MODEL_REGISTRY = Registry[BaseEmbeddingModel]("embedding_model")
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from experiencemaker.schema.trajectory import Message, ActionMessage
|
|||
from experiencemaker.tool.base_tool import BaseTool
|
||||
from experiencemaker.utils.registry import Registry
|
||||
|
||||
LLM_REGISTRY = Registry("llm")
|
||||
|
||||
class BaseLLM(BaseModel, ABC):
|
||||
model_name: str = Field(...)
|
||||
|
|
@ -110,3 +109,6 @@ class BaseLLM(BaseModel, ABC):
|
|||
raise e
|
||||
|
||||
return None
|
||||
|
||||
|
||||
LLM_REGISTRY = Registry[BaseLLM]("llm")
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from experiencemaker.utils.registry import Registry
|
|||
|
||||
class AgentWrapperMixin(BaseModel, ABC):
|
||||
context_generator: BaseContextGenerator | None = Field(default=None)
|
||||
workspace_id: str = Field(default="")
|
||||
|
||||
def execute(self, query: str, **kwargs) -> Trajectory:
|
||||
raise NotImplementedError
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ from typing import List
|
|||
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
from experiencemaker.model.base_embedding_model import BaseEmbeddingModel
|
||||
from experiencemaker.model.base_llm import BaseLLM
|
||||
from experiencemaker.schema.trajectory import Trajectory, ContextMessage
|
||||
from experiencemaker.schema.vector_store_node import VectorStoreNode
|
||||
from experiencemaker.storage.base_vector_store import BaseVectorStore
|
||||
|
|
@ -11,6 +13,9 @@ from experiencemaker.utils.registry import Registry
|
|||
|
||||
class BaseContextGenerator(BaseModel, ABC):
|
||||
vector_store: BaseVectorStore | None = Field(default=None)
|
||||
llm: BaseLLM | None = Field(default=None)
|
||||
embedding_model: BaseEmbeddingModel | None = Field(default=None)
|
||||
workspace_id: str = Field(default="")
|
||||
|
||||
def _build_retrieve_query(self, trajectory: Trajectory, **kwargs) -> str:
|
||||
raise NotImplementedError
|
||||
|
|
|
|||
|
|
@ -3,24 +3,33 @@ from typing import List
|
|||
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
from experiencemaker.schema.trajectory import Trajectory, Sample
|
||||
from experiencemaker.model.base_embedding_model import BaseEmbeddingModel
|
||||
from experiencemaker.model.base_llm import BaseLLM
|
||||
from experiencemaker.schema.experience import Experience
|
||||
from experiencemaker.schema.trajectory import Trajectory
|
||||
from experiencemaker.schema.vector_store_node import VectorStoreNode
|
||||
from experiencemaker.storage.base_vector_store import BaseVectorStore
|
||||
from experiencemaker.utils.registry import Registry
|
||||
|
||||
|
||||
class BaseSummarizer(BaseModel, ABC):
|
||||
vector_store: BaseVectorStore | None = Field(default=None)
|
||||
llm: BaseLLM | None = Field(default=None)
|
||||
embedding_model: BaseEmbeddingModel | None = Field(default=None)
|
||||
workspace_id: str = Field(default="")
|
||||
|
||||
def _extract_samples(self, trajectories: List[Trajectory], **kwargs) -> List[Sample]:
|
||||
def _extract_experiences(self, trajectories: List[Trajectory], **kwargs) -> List[Experience]:
|
||||
raise NotImplementedError
|
||||
|
||||
def _insert_into_database(self, samples: List[Sample], **kwargs):
|
||||
raise NotImplementedError
|
||||
def execute(self, trajectories: List[Trajectory], return_experience: bool = True, **kwargs) -> List[Experience]:
|
||||
experiences: List[Experience] = self._extract_experiences(trajectories, **kwargs)
|
||||
|
||||
def execute(self, trajectories: List[Trajectory], return_samples: bool = True, **kwargs) -> List[Sample]:
|
||||
samples: List[Sample] = self._extract_samples(trajectories, **kwargs)
|
||||
self._insert_into_database(samples, **kwargs)
|
||||
nodes: List[VectorStoreNode] = [x.to_vector_store_node() for x in experiences]
|
||||
self.vector_store.insert(nodes, **kwargs)
|
||||
|
||||
if return_samples:
|
||||
return samples
|
||||
if return_experience:
|
||||
return experiences
|
||||
return []
|
||||
|
||||
return []
|
||||
|
||||
SUMMARIZER_REGISTRY = Registry[BaseSummarizer]("summarizer")
|
||||
|
|
|
|||
68
experiencemaker/module/summarizer/simple_summarizer.py
Normal file
68
experiencemaker/module/summarizer/simple_summarizer.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import Field
|
||||
|
||||
from experiencemaker.enumeration.role import Role
|
||||
from experiencemaker.module.prompt.prompt_mixin import PromptMixin
|
||||
from experiencemaker.module.summarizer.base_summarizer import BaseSummarizer, SUMMARIZER_REGISTRY
|
||||
from experiencemaker.schema.experience import Experience
|
||||
from experiencemaker.schema.trajectory import Trajectory, Message, ActionMessage
|
||||
from experiencemaker.utils.util_function import get_html_match_content
|
||||
|
||||
|
||||
class SimpleSummarizer(BaseSummarizer, PromptMixin):
|
||||
max_retries: int = Field(default=5, description="max retries")
|
||||
prompt_file_path: Path = Path(__file__).parent / "simple_summarizer_prompt.yaml"
|
||||
|
||||
def _extract_trajectory_experience(self, trajectory: Trajectory) -> Experience | None:
|
||||
step_content_collector: List[str] = []
|
||||
|
||||
for step in trajectory.steps:
|
||||
step_index = len(step_content_collector)
|
||||
|
||||
if step.role is Role.ASSISTANT:
|
||||
line = f"### step.{step_index} role={step.role.value} content=\n{step.content}\n{step.reasoning_content}\n"
|
||||
if step.tool_calls:
|
||||
for tool_call in step.tool_calls:
|
||||
line += f" - tool call={tool_call.name}\n params={tool_call.arguments}\n"
|
||||
step_content_collector.append(line)
|
||||
|
||||
elif step.role is Role.USER:
|
||||
line = f"### step.{step_index} role={step.role.value} content=\n{step.content}\n"
|
||||
step_content_collector.append(line)
|
||||
|
||||
elif step.role is Role.TOOL:
|
||||
line = f"### step.{step_index} role={step.role.value} tool call result=\n{step.content}\n"
|
||||
step_content_collector.append(line)
|
||||
|
||||
prompt = self.prompt_format(prompt_name="summary_prompt",
|
||||
query=trajectory.query,
|
||||
execution_process="\n".join(step_content_collector).strip(),
|
||||
answer=trajectory.answer)
|
||||
|
||||
for i in range(self.max_retries):
|
||||
action_message: ActionMessage = self.llm.chat(messages=[Message(content=prompt)])
|
||||
experience_str = get_html_match_content(action_message.content, key="experience")
|
||||
condition_str = get_html_match_content(action_message.content, key="condition")
|
||||
if experience_str and condition_str:
|
||||
return Experience(experience_workspace_id=self.workspace_id,
|
||||
experience_role=self.llm.model_name,
|
||||
experience_desc=condition_str,
|
||||
experience_content=experience_str)
|
||||
else:
|
||||
logger.warning(f"action_message.content={action_message.content} re.search failed.")
|
||||
|
||||
return None
|
||||
|
||||
def _extract_experiences(self, trajectories: List[Trajectory], **kwargs) -> List[Experience]:
|
||||
experiences: List[Experience] = []
|
||||
for trajectory in trajectories:
|
||||
experience: Experience = self._extract_trajectory_experience(trajectory)
|
||||
if experience:
|
||||
experiences.append(experience)
|
||||
return experiences
|
||||
|
||||
|
||||
SUMMARIZER_REGISTRY.register(SimpleSummarizer, "simple")
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
summary_prompt: |
|
||||
# Role
|
||||
You are a helpful assistant named BeyondAgent.
|
||||
|
||||
# User Question
|
||||
{query}
|
||||
|
||||
# Execution Process
|
||||
{execution_process}
|
||||
|
||||
# Answer
|
||||
{answer}
|
||||
|
||||
# Task
|
||||
Reflect on the strengths and weaknesses of the **Execution Process** and the **Answer** based on the **User Question**.
|
||||
Finally, summarize generalized experience from handling such problems to accumulate experience for future similar tasks.
|
||||
The experience should be broadly applicable, such as how to use tools effectively or approaches to solving certain types of problems.
|
||||
Also, specify the conditions or scenarios in which these experience are applicable.
|
||||
|
||||
# Output Format
|
||||
<condition> Output the scenarios or conditions in which applying this experience would be particularly effective... </condition>
|
||||
<experience> Output generalized experience, concise content is required... </experience>
|
||||
83
experiencemaker/schema/experience.py
Normal file
83
experiencemaker/schema/experience.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import datetime
|
||||
from typing import List
|
||||
from uuid import uuid4
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from experiencemaker.schema.vector_store_node import VectorStoreNode
|
||||
|
||||
|
||||
class ExperienceFunctionArg(BaseModel):
|
||||
arg_name: str = Field(default=..., description="argument name")
|
||||
arg_type: str = Field(default=..., description="argument type, like: 'str', 'int', 'bool'")
|
||||
required: bool = Field(default=True, description="whether the argument is required")
|
||||
|
||||
|
||||
class ExperienceFunction(BaseModel):
|
||||
func_code: str = Field(default=..., description="function code")
|
||||
func_name: str = Field(default=..., description="function name")
|
||||
func_args: List[ExperienceFunctionArg] = Field(default_factory=list, description="function arguments")
|
||||
|
||||
|
||||
class Experience(BaseModel):
|
||||
experience_id: str = Field(default_factory=lambda: uuid4().hex, description="experience unique id")
|
||||
experience_workspace_id: str = Field(default="", description="unique workspace id")
|
||||
experience_role: str = Field(default="", description="experience role")
|
||||
experience_desc: str = Field(default="", description="use condition/purpose. It will be used in vector matching")
|
||||
experience_content: str | bytes = Field(default="", description="content of the experience")
|
||||
experience_function: ExperienceFunction | None = Field(default=None, description="experience function(optional)")
|
||||
experience_score: float = Field(default=0.0, description="score of the experience")
|
||||
experience_created_time: str = Field(default_factory=lambda: datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
experience_modified_time: str = Field(default_factory=lambda: datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
metadata: dict = Field(default_factory=dict, description="additional metadata")
|
||||
|
||||
def to_vector_store_node(self) -> VectorStoreNode:
|
||||
metadata: dict = {
|
||||
"experience_role": self.experience_role,
|
||||
"experience_content": self.experience_content,
|
||||
"experience_function": self.experience_function.model_dump(),
|
||||
"experience_score": self.experience_score,
|
||||
"experience_created_time": self.experience_created_time,
|
||||
"experience_modified_time": self.experience_modified_time,
|
||||
"metadata": self.metadata,
|
||||
}
|
||||
return VectorStoreNode(
|
||||
unique_id=self.experience_id,
|
||||
workspace_id=self.experience_workspace_id,
|
||||
content=self.experience_desc,
|
||||
metadata=metadata)
|
||||
|
||||
@classmethod
|
||||
def from_vector_store_node(cls, node: VectorStoreNode) -> "Experience":
|
||||
return cls(
|
||||
experience_id=node.unique_id,
|
||||
experience_workspace_id=node.workspace_id,
|
||||
experience_role=node.metadata.get("experience_role", ""),
|
||||
experience_desc=node.content,
|
||||
experience_content=node.metadata.get("experience_content", ""),
|
||||
experience_function=node.metadata.get("experience_function", None),
|
||||
experience_score=node.metadata.get("experience_score", 0.0),
|
||||
experience_created_time=node.metadata.get("experience_created_time", ""),
|
||||
experience_modified_time=node.metadata.get("experience_modified_time", ""),
|
||||
metadata=node.metadata.get("metadata", {}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
e1 = Experience(
|
||||
experience_workspace_id="w_1024",
|
||||
experience_role="qwen3",
|
||||
experience_desc="test desc",
|
||||
experience_content="test content",
|
||||
experience_function=ExperienceFunction(
|
||||
func_code="def a():\n return",
|
||||
func_name="a",
|
||||
func_args=[ExperienceFunctionArg(arg_name="x", arg_type="str", required=True)]
|
||||
),
|
||||
experience_score=0.99,
|
||||
metadata={"haha": 1}
|
||||
)
|
||||
print(e1.model_dump_json(indent=2))
|
||||
v1 = e1.to_vector_store_node()
|
||||
print(v1.model_dump_json(indent=2))
|
||||
e2 = Experience.from_vector_store_node(v1)
|
||||
print(e2.model_dump_json(indent=2))
|
||||
|
|
@ -118,11 +118,3 @@ class Trajectory(BaseModel):
|
|||
self.query = ""
|
||||
self.answer = ""
|
||||
self.metadata.clear()
|
||||
|
||||
|
||||
class Experience(BaseModel):
|
||||
experience_id: str = Field(default_factory=lambda: uuid4().hex, description="experience unique id")
|
||||
experience_desc: str = Field(default="", description="use condition or use purpose for vector matching")
|
||||
experience_content: str | bytes = Field(default="", description="content of the experience")
|
||||
experience_score: float = Field(default=0.0, description="score of the experience")
|
||||
metadata: dict = Field(default_factory=dict, description="additional metadata")
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
from experiencemaker.utils.registry import Registry
|
||||
|
||||
VECTOR_STORE_REGISTRY = Registry("vector_store")
|
||||
|
|
@ -5,6 +5,7 @@ from pydantic import BaseModel, Field
|
|||
|
||||
from experiencemaker.model.base_embedding_model import BaseEmbeddingModel
|
||||
from experiencemaker.schema.vector_store_node import VectorStoreNode
|
||||
from experiencemaker.utils.registry import Registry
|
||||
|
||||
|
||||
class BaseVectorStore(BaseModel, ABC):
|
||||
|
|
@ -24,3 +25,6 @@ class BaseVectorStore(BaseModel, ABC):
|
|||
|
||||
def retrieve_by_query(self, query: str, top_k: int = 3, **kwargs) -> List[VectorStoreNode]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
VECTOR_STORE_REGISTRY = Registry[BaseVectorStore]("vector_store")
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ from loguru import logger
|
|||
from pydantic import Field, PrivateAttr, model_validator
|
||||
|
||||
from experiencemaker.schema.vector_store_node import VectorStoreNode
|
||||
from experiencemaker.storage import VECTOR_STORE_REGISTRY
|
||||
from experiencemaker.storage.base_vector_store import BaseVectorStore
|
||||
from experiencemaker.storage.base_vector_store import BaseVectorStore, VECTOR_STORE_REGISTRY
|
||||
|
||||
|
||||
class EsVectorStore(BaseVectorStore):
|
||||
|
|
@ -104,7 +103,7 @@ class EsVectorStore(BaseVectorStore):
|
|||
self.retrieve_filters.clear()
|
||||
return self
|
||||
|
||||
def insert(self, nodes: VectorStoreNode | List[VectorStoreNode], refresh_index: bool = False, **kwargs):
|
||||
def insert(self, nodes: VectorStoreNode | List[VectorStoreNode], refresh_index: bool = True, **kwargs):
|
||||
if isinstance(nodes, VectorStoreNode):
|
||||
nodes = [nodes]
|
||||
|
||||
|
|
@ -119,7 +118,7 @@ class EsVectorStore(BaseVectorStore):
|
|||
if refresh_index:
|
||||
self.refresh_index()
|
||||
|
||||
def update(self, nodes: VectorStoreNode | List[VectorStoreNode], refresh_index: bool = False, **kwargs):
|
||||
def update(self, nodes: VectorStoreNode | List[VectorStoreNode], refresh_index: bool = True, **kwargs):
|
||||
if isinstance(nodes, VectorStoreNode):
|
||||
nodes = [nodes]
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ from loguru import logger
|
|||
from pydantic import Field, model_validator, PrivateAttr
|
||||
|
||||
from experiencemaker.schema.vector_store_node import VectorStoreNode
|
||||
from experiencemaker.storage import VECTOR_STORE_REGISTRY
|
||||
from experiencemaker.storage.base_vector_store import BaseVectorStore
|
||||
from experiencemaker.storage.base_vector_store import BaseVectorStore, VECTOR_STORE_REGISTRY
|
||||
|
||||
|
||||
class FileVectorStore(BaseVectorStore):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue