From f377d3a8a97b035e122d1a27c94c254484ffee31 Mon Sep 17 00:00:00 2001 From: "jinli.yl" Date: Thu, 10 Jul 2025 15:34:55 +0800 Subject: [PATCH] move code --- experiencemaker/em_client.py | 24 -- experiencemaker/em_service.py | 297 ------------------ experiencemaker/enumeration/__init__.py | 0 experiencemaker/enumeration/chunk_enum.py | 9 - experiencemaker/enumeration/http_enum.py | 6 - experiencemaker/enumeration/role.py | 11 - experiencemaker/model/__init__.py | 2 - experiencemaker/model/base_embedding_model.py | 90 ------ experiencemaker/model/base_llm.py | 114 ------- .../openai_compatible_embedding_model.py | 91 ------ experiencemaker/tool/__init__.py | 3 - experiencemaker/tool/base_tool.py | 78 ----- experiencemaker/utils/registry.py | 31 -- v1/embedding_model/base_embedding_model.py | 16 - .../openai_compatible_embedding_model.py | 43 +-- .../enumeration/agent_state.py | 0 v1/llm/base_llm.py | 10 +- .../model => v1/llm}/openai_compatible_llm.py | 47 +-- v1/op/base_op.py | 5 +- v1/op/prompt_mixin.py | 66 ++++ v1/service/experience_maker_client.py | 31 ++ v1/service/experience_maker_service.py | 2 +- {experiencemaker => v1}/tool/code_tool.py | 2 +- .../tool/dashscope_search_tool.py | 6 +- {experiencemaker => v1}/tool/mcp_tool.py | 2 +- .../tool/terminate_tool.py | 2 +- {experiencemaker => v1}/utils/file_handler.py | 10 +- {experiencemaker => v1}/utils/http_client.py | 2 +- v1/utils/timer.py | 5 +- 29 files changed, 139 insertions(+), 866 deletions(-) delete mode 100644 experiencemaker/em_client.py delete mode 100644 experiencemaker/em_service.py delete mode 100644 experiencemaker/enumeration/__init__.py delete mode 100644 experiencemaker/enumeration/chunk_enum.py delete mode 100644 experiencemaker/enumeration/http_enum.py delete mode 100644 experiencemaker/enumeration/role.py delete mode 100644 experiencemaker/model/__init__.py delete mode 100644 experiencemaker/model/base_embedding_model.py delete mode 100644 experiencemaker/model/base_llm.py delete mode 100644 experiencemaker/model/openai_compatible_embedding_model.py delete mode 100644 experiencemaker/tool/__init__.py delete mode 100644 experiencemaker/tool/base_tool.py delete mode 100644 experiencemaker/utils/registry.py rename {experiencemaker => v1}/enumeration/agent_state.py (100%) rename {experiencemaker/model => v1/llm}/openai_compatible_llm.py (77%) create mode 100644 v1/op/prompt_mixin.py create mode 100644 v1/service/experience_maker_client.py rename {experiencemaker => v1}/tool/code_tool.py (95%) rename {experiencemaker => v1}/tool/dashscope_search_tool.py (97%) rename {experiencemaker => v1}/tool/mcp_tool.py (98%) rename {experiencemaker => v1}/tool/terminate_tool.py (93%) rename {experiencemaker => v1}/utils/file_handler.py (68%) rename {experiencemaker => v1}/utils/http_client.py (99%) diff --git a/experiencemaker/em_client.py b/experiencemaker/em_client.py deleted file mode 100644 index d9bf6fe7..00000000 --- a/experiencemaker/em_client.py +++ /dev/null @@ -1,24 +0,0 @@ -from pydantic import Field - -from experiencemaker.schema.request import AgentWrapperRequest, ContextGeneratorRequest, SummarizerRequest -from experiencemaker.schema.response import AgentWrapperResponse, ContextGeneratorResponse, SummarizerResponse -from experiencemaker.utils.http_client import HttpClient - - -class EMClient(HttpClient): - base_url: str = Field(default=...) - - def call_agent_wrapper(self, request: AgentWrapperRequest): - self.url = self.base_url + "/agent_wrapper" - return AgentWrapperResponse(**self.request(json_data=request.model_dump(), - headers={"Content-Type": "application/json"})) - - def call_context_generator(self, request: ContextGeneratorRequest): - self.url = self.base_url + "/context_generator" - return ContextGeneratorResponse(**self.request(json_data=request.model_dump(), - headers={"Content-Type": "application/json"})) - - def call_summarizer(self, request: SummarizerRequest): - self.url = self.base_url + "/summarizer" - return SummarizerResponse(**self.request(json_data=request.model_dump(), - headers={"Content-Type": "application/json"})) diff --git a/experiencemaker/em_service.py b/experiencemaker/em_service.py deleted file mode 100644 index 4cc1885d..00000000 --- a/experiencemaker/em_service.py +++ /dev/null @@ -1,297 +0,0 @@ -import argparse -import copy -import json -import types -from typing import List - -import uvicorn -from fastapi import FastAPI -from loguru import logger -from pydantic import BaseModel, Field, model_validator - -from experiencemaker.utils.util_function import load_env_keys - -load_env_keys() - -from experiencemaker.model.base_embedding_model import BaseEmbeddingModel, EMBEDDING_MODEL_REGISTRY -from experiencemaker.model.base_llm import BaseLLM, LLM_REGISTRY -from experiencemaker.module.agent_wrapper.agent_wrapper_mixin import AgentWrapperMixin, AGENT_WRAPPER_REGISTRY -from experiencemaker.module.context_generator.base_context_generator import BaseContextGenerator, \ - CONTEXT_GENERATOR_REGISTRY -from experiencemaker.module.summarizer.base_summarizer import BaseSummarizer, SUMMARIZER_REGISTRY -from experiencemaker.schema.experience import Experience -from experiencemaker.schema.request import AgentWrapperRequest, ContextGeneratorRequest, SummarizerRequest -from experiencemaker.schema.response import AgentWrapperResponse, ContextGeneratorResponse, SummarizerResponse -from experiencemaker.schema.trajectory import Trajectory, ContextMessage -from experiencemaker.storage.base_vector_store import BaseVectorStore, VECTOR_STORE_REGISTRY - - -class EMService(BaseModel): - host: str = Field(default="0.0.0.0") - port: int = Field(default=8001) - timeout_keep_alive: int = Field(default=600000) - limit_concurrency: int = Field(default=128) - - llm: BaseLLM | None = Field(default=None) - embedding_model: BaseEmbeddingModel | None = Field(default=None) - vector_store: BaseVectorStore | None = Field(default=None) - agent_wrapper: AgentWrapperMixin | None = Field(default=None) - context_generator: BaseContextGenerator | None = Field(default=None) - summarizer: BaseSummarizer | None = Field(default=None) - - origin_config: dict = Field(default_factory=dict) - - @staticmethod - def init_llm(llm_config: dict) -> BaseLLM: - backend = llm_config.pop("backend", None) - assert backend is not None, "llm must have a backend like `openai_compatible`." - assert backend in LLM_REGISTRY, f"llm backend={backend} not supported. " \ - f"supported={LLM_REGISTRY.registered_module_names}" - llm = LLM_REGISTRY[backend](**llm_config) - logger.info(f"llm is inited with backend={backend} params={llm_config}") - return llm - - @classmethod - def get_llm(cls, config: dict, llm: BaseLLM = None) -> BaseLLM: - if "llm" in config: - llm_config = config.pop("llm") - llm = cls.init_llm(llm_config) - elif llm is None: - raise RuntimeError("llm must be provided.") - return llm - - @staticmethod - def init_embedding_model(embedding_model_config: dict) -> BaseEmbeddingModel: - backend = embedding_model_config.pop("backend", None) - assert backend is not None, "embedding_model must have a backend like `openai_compatible`." - assert backend in EMBEDDING_MODEL_REGISTRY, f"embedding_model backend={backend} not supported. " \ - f"supported={EMBEDDING_MODEL_REGISTRY.registered_module_names}" - embedding_model = EMBEDDING_MODEL_REGISTRY[backend](**embedding_model_config) - logger.info(f"embedding_model is inited with backend={backend} params={embedding_model_config}") - return embedding_model - - @classmethod - def get_embedding_model(cls, config: dict, embedding_model: BaseEmbeddingModel = None) -> BaseEmbeddingModel: - if "embedding_model" in config: - embedding_model_config = config.pop("embedding_model") - embedding_model = cls.init_embedding_model(embedding_model_config) - elif embedding_model is None: - raise RuntimeError("embedding_model must be provided.") - return embedding_model - - @classmethod - def init_vector_store(cls, vector_store_config: dict, - embedding_model: BaseEmbeddingModel = None) -> BaseVectorStore: - backend = vector_store_config.pop("backend", None) - assert backend is not None, "vector_store must have a backend like `elasticsearch`." - assert backend in VECTOR_STORE_REGISTRY, f"vector_store backend={backend} not supported. " \ - f"supported={VECTOR_STORE_REGISTRY.registered_module_names}" - embedding_model = cls.get_embedding_model(vector_store_config, embedding_model=embedding_model) - vector_store = VECTOR_STORE_REGISTRY[backend](**vector_store_config, embedding_model=embedding_model) - logger.info(f"vector_store is inited with backend={backend} params={vector_store_config}") - return vector_store - - @classmethod - def get_vector_store(cls, config: dict, vector_store: BaseVectorStore = None, - embedding_model: BaseEmbeddingModel = None) -> BaseVectorStore: - if "vector_store" in config: - vector_store_config = config.pop("vector_store") - vector_store = cls.init_vector_store(vector_store_config, embedding_model=embedding_model) - elif vector_store is None: - raise RuntimeError("vector_store must be provided.") - return vector_store - - @classmethod - def init_context_generator(cls, context_generator_config: dict, data: dict) -> BaseContextGenerator: - backend = context_generator_config.pop("backend", None) - assert backend is not None, "context_generator must have a backend like `simple`." - assert backend in CONTEXT_GENERATOR_REGISTRY, f"context_generator backend={backend} not supported. " \ - f"supported={CONTEXT_GENERATOR_REGISTRY.registered_module_names}" - - llm = cls.get_llm(context_generator_config, llm=data.get("llm")) - vector_store = cls.get_vector_store(context_generator_config, vector_store=data.get("vector_store"), - embedding_model=data.get("embedding_model")) - - context_generator: BaseContextGenerator = CONTEXT_GENERATOR_REGISTRY[backend]( - **context_generator_config, llm=llm, vector_store=vector_store) - logger.info(f"context_generator is inited with backend={backend} params={context_generator_config}") - return context_generator - - @classmethod - def init_summarizer(cls, summarizer_config: dict, data: dict) -> BaseSummarizer: - backend = summarizer_config.pop("backend", None) - assert backend is not None, "summarizer must have a backend like `simple`." - assert backend in SUMMARIZER_REGISTRY, f"summarizer backend={backend} not supported. " \ - f"supported={SUMMARIZER_REGISTRY.registered_module_names}" - - llm = cls.get_llm(summarizer_config, llm=data.get("llm")) - vector_store = cls.get_vector_store(summarizer_config, vector_store=data.get("vector_store"), - embedding_model=data.get("embedding_model")) - summarizer: BaseSummarizer = SUMMARIZER_REGISTRY[backend]( - **summarizer_config, llm=llm, vector_store=vector_store) - logger.info(f"summarizer is inited with backend={backend} params={summarizer_config}") - return summarizer - - @classmethod - def init_agent_wrapper(cls, agent_wrapper_config: dict, data: dict) -> AgentWrapperMixin: - backend = agent_wrapper_config.pop("backend", None) - assert backend is not None, "agent_wrapper must have a backend like `simple`." - assert backend in AGENT_WRAPPER_REGISTRY, f"agent_wrapper backend={backend} not supported. " \ - f"supported={AGENT_WRAPPER_REGISTRY.registered_module_names}" - - llm = cls.get_llm(agent_wrapper_config, llm=data.get("llm")) - agent_wrapper: AgentWrapperMixin = AGENT_WRAPPER_REGISTRY[backend]( - **agent_wrapper_config, llm=llm, context_generator=data.get("context_generator")) - logger.info(f"agent_wrapper is inited with backend={backend} params={agent_wrapper_config}") - return agent_wrapper - - @classmethod - def init_class_by_config(cls, data: dict): - try: - if "llm" in data: - data["llm"] = cls.init_llm(data["llm"]) - - if "embedding_model" in data: - data["embedding_model"] = cls.init_embedding_model(data["embedding_model"]) - - if "vector_store" in data: - data["vector_store"] = cls.init_vector_store(data["vector_store"], - embedding_model=data["embedding_model"]) - - if "context_generator" in data: - data["context_generator"] = cls.init_context_generator(data["context_generator"], data) - - if "summarizer" in data: - data["summarizer"] = cls.init_summarizer(data["summarizer"], data) - - if "agent_wrapper" in data: - data["agent_wrapper"] = cls.init_agent_wrapper(data["agent_wrapper"], data) - - except Exception as e: - logger.exception(e.args) - return data - - @model_validator(mode="before") # noqa - @classmethod - def init_modules(cls, data: dict): - origin_config = copy.deepcopy(data) - data = cls.init_class_by_config(data) - data["origin_config"] = origin_config - return data - - def call_agent_wrapper(self, request: AgentWrapperRequest) -> AgentWrapperResponse: - if "em_config" in request.metadata: - new_config = copy.deepcopy(self.origin_config) - new_config.update(request.metadata["em_config"]) - data = EMService.init_class_by_config(new_config) - agent_wrapper = data["agent_wrapper"] - else: - assert self.agent_wrapper is not None, "agent_wrapper must be provided." - agent_wrapper = self.agent_wrapper - - trajectory: Trajectory = agent_wrapper.execute(query=request.query, - workspace_id=request.workspace_id, - **request.metadata) - return AgentWrapperResponse(trajectory=trajectory) - - def call_context_generator(self, request: ContextGeneratorRequest) -> ContextGeneratorResponse: - if "em_config" in request.metadata: - new_config = copy.deepcopy(self.origin_config) - new_config.update(request.metadata["em_config"]) - data = EMService.init_class_by_config(new_config) - context_generator = data["context_generator"] - else: - assert self.context_generator is not None, "context_generator must be provided." - context_generator = self.context_generator - logger.info(f"workspace_id={request.workspace_id} metadata={request.metadata} " - f"trajectory=\n{request.trajectory.model_dump_json(indent=2)}") - context_msg: ContextMessage = context_generator.execute(trajectory=request.trajectory, - workspace_id=request.workspace_id, - **request.metadata) - logger.info(f"workspace_id={request.workspace_id} context_msg={context_msg.model_dump_json(indent=2)}") - return ContextGeneratorResponse(context_msg=context_msg) - - def call_summarizer(self, request: SummarizerRequest) -> SummarizerResponse: - if "em_config" in request.metadata: - new_config = copy.deepcopy(self.origin_config) - new_config.update(request.metadata["em_config"]) - data = EMService.init_class_by_config(new_config) - summarizer = data["summarizer"] - else: - assert self.summarizer is not None, "summarizer must be provided." - summarizer = self.summarizer - - trajectories_content = "\n".join([x.model_dump_json(indent=2) for x in request.trajectories]) - logger.info(f"workspace_id={request.workspace_id} metadata={request.metadata} " - f"trajectories=\n{trajectories_content}") - experiences: List[Experience] = summarizer.execute(trajectories=request.trajectories, - workspace_id=request.workspace_id, - **request.metadata) - - experiences_content = "\n".join([x.model_dump_json(indent=2) for x in experiences]) - logger.info(f"workspace_id={request.workspace_id} experiences_content=\n{experiences_content}") - return SummarizerResponse(experiences=experiences) - - -app = FastAPI() -service: EMService | None = None - - -@app.post('/agent_wrapper', response_model=AgentWrapperResponse) -def call_agent_wrapper(request: AgentWrapperRequest): - return service.call_agent_wrapper(request) - - -@app.post('/context_generator', response_model=ContextGeneratorResponse) -def call_context_generator(request: ContextGeneratorRequest): - return service.call_context_generator(request) - - -@app.post('/summarizer', response_model=SummarizerResponse) -def call_summarizer(request: SummarizerRequest): - return service.call_summarizer(request) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - field_dict = EMService.model_fields - assert isinstance(field_dict, dict) - - json_keys = [] - for key, info in field_dict.items(): - if info.annotation in [int, str, bool]: - parser.add_argument(f"--{key}", type=info.annotation, default=info.default) - - elif isinstance(info.annotation, types.UnionType) and issubclass(info.annotation.__args__[0], BaseModel): - parser.add_argument(f"--{key}", type=str, default=None) - json_keys.append(key) - - elif info.annotation in [dict, list]: - logger.warning(f"skip key={key} info.annotation={info.annotation}") - continue - - else: - raise NotImplementedError(f"key={key} annotation={info.annotation} is not supported.") - - args: argparse.Namespace = parser.parse_args() - service_kwargs = {k: json.loads(v) if k in json_keys else v for k, v in args.__dict__.items() if v is not None} - logger.info(f"service.kwargs={json.dumps(service_kwargs, indent=2, ensure_ascii=False)}") - - service = EMService(**service_kwargs) - uvicorn.run(app, - host=service.host, - port=service.port, - timeout_keep_alive=service.timeout_keep_alive, - limit_concurrency=service.limit_concurrency) - -""" -launch with: -python -m experiencemaker.em_service \ - --port=8001 \ - --llm='{"backend": "openai_compatible", "model_name": "qwen3-32b", "temperature": 0.6}' \ - --embedding_model='{"backend": "openai_compatible", "model_name": "text-embedding-v4", "dimensions": 1024}' \ - --vector_store='{"backend": "elasticsearch"}' \ - --agent_wrapper='{"backend": "simple"}' \ - --context_generator='{"backend": "simple"}' \ - --summarizer='{"backend": "simple"}' -""" diff --git a/experiencemaker/enumeration/__init__.py b/experiencemaker/enumeration/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/experiencemaker/enumeration/chunk_enum.py b/experiencemaker/enumeration/chunk_enum.py deleted file mode 100644 index 975f0b1f..00000000 --- a/experiencemaker/enumeration/chunk_enum.py +++ /dev/null @@ -1,9 +0,0 @@ -from enum import Enum - - -class ChunkEnum(str, Enum): - THINK = "think" - ANSWER = "answer" - TOOL = "tool" - USAGE = "usage" - ERROR = "error" diff --git a/experiencemaker/enumeration/http_enum.py b/experiencemaker/enumeration/http_enum.py deleted file mode 100644 index 69354c45..00000000 --- a/experiencemaker/enumeration/http_enum.py +++ /dev/null @@ -1,6 +0,0 @@ -from enum import Enum - - -class HttpEnum(str, Enum): - GET = "get" - POST = "post" diff --git a/experiencemaker/enumeration/role.py b/experiencemaker/enumeration/role.py deleted file mode 100644 index ae33f0e0..00000000 --- a/experiencemaker/enumeration/role.py +++ /dev/null @@ -1,11 +0,0 @@ -from enum import Enum - - -class Role(str, Enum): - SYSTEM = "system" - USER = "user" - TOOL = "tool" # environment - - ASSISTANT = "assistant" # policy model - CONTEXT_ASSISTANT = "context_assistant" # context model - SUMMARY_ASSISTANT = "summary_assistant" # summary model diff --git a/experiencemaker/model/__init__.py b/experiencemaker/model/__init__.py deleted file mode 100644 index 7d7d977f..00000000 --- a/experiencemaker/model/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from experiencemaker.model.openai_compatible_embedding_model import OpenAICompatibleEmbeddingModel -from experiencemaker.model.openai_compatible_llm import OpenAICompatibleBaseLLM diff --git a/experiencemaker/model/base_embedding_model.py b/experiencemaker/model/base_embedding_model.py deleted file mode 100644 index 93891900..00000000 --- a/experiencemaker/model/base_embedding_model.py +++ /dev/null @@ -1,90 +0,0 @@ -from abc import ABC -from typing import List - -from loguru import logger -from pydantic import BaseModel, Field - -from experiencemaker.schema.vector_store_node import VectorStoreNode -from experiencemaker.utils.registry import Registry - -EMBEDDING_MODEL_REGISTRY = Registry() - -class BaseEmbeddingModel(BaseModel, ABC): - model_name: str = Field(default=..., description="model name") - dimensions: int = Field(default=..., description="dimensions") - max_retries: int = Field(default=3, description="max retries") - raise_exception: bool = Field(default=True, description="raise exception") - - def _get_embeddings(self, input_text: str | List[str]): - """ - Get the embedding vector based on the input text. - This is an abstract method, and its concrete implementation must be provided in a subclass to generate the embedding vector for the given text. - Args: - input_text (str | List[str]): The input text, which can be a single string or a list of strings. - Raises: - NotImplementedError: If the method is not implemented in the subclass. - """ - raise NotImplementedError - - def get_embeddings(self, input_text: str | List[str]): - """ - Retrieves embeddings for the input text. - - This function attempts to obtain embeddings for the given input text. It will retry a maximum number of times in case of failure. - - Parameters: - - input_text (str | List[str]): The input text, which can be a single string or a list of strings. - - Returns: - - embeddings: The embeddings for the input text. Returns None if the maximum number of retries is reached and no successful result is obtained. - """ - # Attempt to get embeddings, with a maximum number of retries set - for i in range(self.max_retries): - try: - # Attempt to get embeddings, return immediately if successful - return self._get_embeddings(input_text) - - except Exception as e: - # Log exception information when an error occurs - logger.exception(f"embedding model name={self.model_name} encounter error with e={e.args}") - - # If the maximum number of retries is reached and raise_exception is set to True, re-throw the exception - if i == self.max_retries - 1 and self.raise_exception: - raise e - - return None - - def get_node_embeddings(self, nodes: VectorStoreNode | List[VectorStoreNode]): - """ - Assigns embeddings to the nodes based on their content. - - This function accepts either a single VectorStoreNode or a list of VectorStoreNodes. - It retrieves the embedding for the content of each node and assigns it to the node's vector attribute. - If a list of nodes is provided, it performs a batch retrieval of embeddings. - - Parameters: - - nodes (VectorStoreNode | List[VectorStoreNode]): A single node or list of nodes whose embeddings need to be retrieved. - - Returns: - - VectorStoreNode | List[VectorStoreNode]: Returns the input nodes with their vector attribute populated with embeddings. - - Raises: - - RuntimeError: If the input is neither a VectorStoreNode nor a list of VectorStoreNodes, a RuntimeError is raised. - """ - if isinstance(nodes, VectorStoreNode): - nodes.vector = self.get_embeddings(nodes.content) - return nodes - - elif isinstance(nodes, list): - max_batch_size = 10 # text-embedding-v4 batch size should not be larger than 10 - embeddings = [emb for i in range(0, len(nodes), max_batch_size) for emb in - self.get_embeddings(input_text=[node.content for node in nodes[i:i + max_batch_size]])] - if len(embeddings) != len(nodes): - logger.warning(f"embeddings.size={len(embeddings)} <> nodes.size={len(nodes)}") - else: - for node, embedding in zip(nodes, embeddings): - node.vector = embedding - return nodes - - else: - raise RuntimeError(f"unsupported type={type(nodes)}") diff --git a/experiencemaker/model/base_llm.py b/experiencemaker/model/base_llm.py deleted file mode 100644 index e0dd53ef..00000000 --- a/experiencemaker/model/base_llm.py +++ /dev/null @@ -1,114 +0,0 @@ -import time -from abc import ABC -from typing import List, Literal - -from loguru import logger -from pydantic import Field, BaseModel - -from experiencemaker.schema.trajectory import Message, ActionMessage -from experiencemaker.tool.base_tool import BaseTool -from experiencemaker.utils.registry import Registry - -LLM_REGISTRY = Registry() - -class BaseLLM(BaseModel, ABC): - model_name: str = Field(...) - - seed: int = Field(default=42) - top_p: float | None = Field(default=None) - # stream: bool = Field(default=True) - stream_options: dict = Field(default={"include_usage": True}) - temperature: float = Field(default=0.0000001) - presence_penalty: float | None = Field(default=None) - enable_thinking: bool = Field(default=True, description="whether the current mode is the reasoning model, " - "or whether Qwen3's reasoning mode is currently enabled.") - tool_choice: Literal["none", "auto", "required"] = Field(default="auto", description="tool choice") - parallel_tool_calls: bool = Field(default=True) - - max_retries: int = Field(default=5, description="max retries") - raise_exception: bool = Field(default=True, description="raise exception") - - def stream_chat(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs): - """ - This method is designed to handle streaming chat functionality, allowing for interactive communication - with the ability to use various tools. It is intended to be overridden by subclasses to implement - specific streaming chat logic. - - Parameters: - - messages: A list of Message objects, representing the message history or current messages in the chat. - - tools: An optional list of BaseTool objects, representing the tools available for use during the chat. - - **kwargs: Additional keyword arguments for future expansion or specific implementations. - - Raises: - - NotImplementedError: This method raises a NotImplementedError to indicate that the functionality - should be implemented by subclasses. - """ - raise NotImplementedError - - def stream_print(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs): - """ - This method is intended to be overridden by subclasses to implement specific message streaming printing logic. - The method raises a NotImplementedError, indicating that this is an abstract method that must be implemented by subclasses. - - Parameters: - - messages: A list of Message objects, representing the messages to be printed. - - tools: An optional list of BaseTool objects, representing auxiliary tools that may be needed during the printing process. - - **kwargs: Additional keyword arguments, allowing for flexible handling of extra parameters. - - Raises: - - NotImplementedError: Indicates that the method is abstract and needs to be implemented by a subclass. - """ - raise NotImplementedError - - def _chat(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs) -> ActionMessage: - """ - Abstract method for processing chat messages and generating responses. - - This method is designed to be overridden by subclasses to implement specific chat logic. - It receives a list of messages as input, along with optional tools, and is expected to return - an ActionMessage object as a response. The method raises a NotImplementedError to enforce - implementation by subclasses. - - Parameters: - - messages: List[Message] - A list of Message objects representing the chat history or current messages. - - tools: List[BaseTool] (optional) - A list of BaseTool objects representing the tools available for use during the chat. Defaults to None. - - **kwargs: Additional keyword arguments for extensibility and backwards compatibility. - - Returns: - - ActionMessage: The response generated based on the input messages, encapsulated in an ActionMessage object. - """ - raise NotImplementedError - - def chat(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs) -> ActionMessage | None: - """ - Initiates a chat session with a model, allowing for the execution of tools. - - This function sends a series of messages to the model and expects to receive an execution response. - It can handle exceptions during the chat process by retrying a set number of times. - - Parameters: - - messages (List[Message]): A list of message objects, containing the conversation history. - - tools (List[BaseTool], optional): A list of tool objects that can be used during the chat. Defaults to None. - - **kwargs: Additional parameters that can be passed to the model. - - Returns: - - ActionMessage: A response message containing the model's execution results. - - None: Returns None if the maximum number of retries is reached and no successful response is obtained. - """ - - # Iterate according to the maximum number of retries set - for i in range(self.max_retries): - try: - # Attempt to execute the chat logic - return self._chat(messages, tools, **kwargs) - - except Exception as e: - # Log exceptions during the chat process - logger.exception(f"chat with model={self.model_name} encounter error with e={e.args}") - time.sleep(1 + i) - - # If the maximum number of retries is reached and raise_exception is set to True, then re-throw the exception - if i == self.max_retries - 1 and self.raise_exception: - raise e - - return None diff --git a/experiencemaker/model/openai_compatible_embedding_model.py b/experiencemaker/model/openai_compatible_embedding_model.py deleted file mode 100644 index 5551bcb2..00000000 --- a/experiencemaker/model/openai_compatible_embedding_model.py +++ /dev/null @@ -1,91 +0,0 @@ -import os -from typing import Literal, List - -from openai import OpenAI -from pydantic import Field, PrivateAttr, model_validator - -from experiencemaker.model.base_embedding_model import BaseEmbeddingModel, EMBEDDING_MODEL_REGISTRY - - -@EMBEDDING_MODEL_REGISTRY.register("openai_compatible") -class OpenAICompatibleEmbeddingModel(BaseEmbeddingModel): - api_key: str = Field(default_factory=lambda: os.getenv("OPENAI_API_KEY"), description="api key") - base_url: str = Field(default_factory=lambda: os.getenv("OPENAI_BASE_URL"), description="base url") - model_name: str = Field(default="text-embedding-v4", description="model name") - dimensions: int = Field(default=1024, description="dimensions") - encoding_format: Literal["float", "base64"] = Field(default="float", description="encoding_format") - _client: OpenAI = PrivateAttr() - - @model_validator(mode="after") - def init_client(self): - """ - Initialize the OpenAI client after model validation. - - This method is called after the model's data has been validated, - ensuring that all necessary attributes are correctly set before - initializing the OpenAI client. It creates an instance of the OpenAI - client using the provided API key and base URL, storing it in the - - Returns: - self: Returns the instance of the current class for method chaining. - """ - self._client = OpenAI(api_key=self.api_key, base_url=self.base_url) - return self - - def _get_embeddings(self, input_text: str | List[str]): - """ - Generate embeddings for the input text. - - This method accepts either a single string or a list of strings as input, - and returns the corresponding embeddings based on the specified model. - - Parameters: - - input_text (str | List[str]): The input text, which can be a single string or a list of strings. - - Returns: - - List[float]: If the input is a single string, returns a list of floating-point numbers representing the embedding. - - List[List[float]]: If the input is a list of strings, returns a list where each item is a list of floating-point numbers representing the embedding of each string. - - Raises RuntimeError: If the input type is unsupported. - """ - - # Create embeddings using the specified model, input text, dimensions, and encoding format - completion = self._client.embeddings.create( - model=self.model_name, - input=input_text, - dimensions=self.dimensions, - encoding_format=self.encoding_format - ) - - # Determine the type of input and process accordingly - if isinstance(input_text, str): - # If the input is a single string, return the embedding of that string - return completion.data[0].embedding - - elif isinstance(input_text, list): - # If the input is a list of strings, initialize a list to hold the embeddings of each string - result_emb = [[] for _ in range(len(input_text))] - # Iterate through the generated embeddings and assign them to the corresponding positions in the result list - for emb in completion.data: - result_emb[emb.index] = emb.embedding - return result_emb - - else: - # If the input type is neither a string nor a list of strings, throw an exception - raise RuntimeError(f"unsupported type={type(input_text)}") - - -def main(): - from experiencemaker.utils.util_function import load_env_keys - load_env_keys() - - model = OpenAICompatibleEmbeddingModel(dimensions=64, model_name="text-embedding-v4") - res1 = model.get_embeddings( - "The clothes are of good quality and look good, definitely worth the wait. I love them.") - res2 = model.get_embeddings(["aa", "bb"]) - print(res1) - print(res2) - - -if __name__ == "__main__": - main() - # launch with: python -m experiencemaker.model.openai_compatible_embedding_model diff --git a/experiencemaker/tool/__init__.py b/experiencemaker/tool/__init__.py deleted file mode 100644 index 925b94c8..00000000 --- a/experiencemaker/tool/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from experiencemaker.tool.code_tool import CodeTool -from experiencemaker.tool.dashscope_search_tool import DashscopeSearchTool -from experiencemaker.tool.terminate_tool import TerminateTool diff --git a/experiencemaker/tool/base_tool.py b/experiencemaker/tool/base_tool.py deleted file mode 100644 index 6243fd51..00000000 --- a/experiencemaker/tool/base_tool.py +++ /dev/null @@ -1,78 +0,0 @@ -from abc import ABC - -from loguru import logger -from pydantic import BaseModel, Field - - -class BaseTool(BaseModel, ABC): - tool_id: str = Field(default="") - name: str = Field(..., description="tool name") - description: str = Field(..., description="tool description") - tool_type: str = Field(default="function") - parameters: dict = Field(default_factory=dict, description="tool parameters") - arguments: dict = Field(default_factory=dict, description="execute arguments") - - enable_cache: bool = Field(default=False, description="whether to cache the tool result") - cached_result: dict = Field(default_factory=dict, description="tool execution result") - - max_retries: int = Field(default=3, description="max retries") - raise_exception: bool = Field(default=True, description="raise exception") - success: bool = Field(default=True, description="whether the tool executed successfully") - - def reset(self): - self.arguments.clear() - self.success = True - - def _execute(self, **kwargs): - raise NotImplementedError - - def execute(self, **kwargs): - cache_id = "" - if self.enable_cache: - cache_id = self.get_cache_id(**kwargs) - if cache_id in self.cached_result: - return self.cached_result[cache_id] - - for i in range(self.max_retries): - try: - if self.enable_cache: - self.cached_result[cache_id] = self._execute(**kwargs) - return self.cached_result[cache_id] - - else: - return self._execute(**kwargs) - - except Exception as e: - logger.exception(f"using tool.name={self.name} encounter error with e={e.args}") - if i == self.max_retries - 1 and self.raise_exception: - raise e - - return None - - # It may be in other different tool params formats; different versions are completed here. - - @property - def simple_dict(self) -> dict: - return { - "type": self.tool_type, - self.tool_type: { - "name": self.name, - "description": self.description, - "parameters": self.parameters, - }, - } - - @property - def input_schema(self) -> dict: - return self.parameters.get("properties", {}) - - @property - def output_schema(self) -> dict: - raise NotImplementedError - - def refresh(self): - # for mcp - raise NotImplementedError - - def get_cache_id(self, **kwargs) -> str: - raise NotImplementedError diff --git a/experiencemaker/utils/registry.py b/experiencemaker/utils/registry.py deleted file mode 100644 index 84888d14..00000000 --- a/experiencemaker/utils/registry.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import List - -from loguru import logger - - -class Registry(object): - def __init__(self): - self._registry = {} - - def register(self, name: str = None): - - def decorator(cls): - class_name = name if name is not None else cls.__name__ - if class_name in self._registry: - logger.warning(f"name={class_name} is already registered, will be overwritten.") - self._registry[class_name] = cls - return cls - - return decorator - - def __getitem__(self, name: str): - if name not in self._registry: - raise KeyError(f"name={name} is not registered!") - return self._registry[name] - - def __contains__(self, name: str): - return name in self._registry - - @property - def registered_module_names(self) -> List[str]: - return sorted(self._registry.keys()) diff --git a/v1/embedding_model/base_embedding_model.py b/v1/embedding_model/base_embedding_model.py index a6a96fd6..d34f5636 100644 --- a/v1/embedding_model/base_embedding_model.py +++ b/v1/embedding_model/base_embedding_model.py @@ -17,28 +17,12 @@ class BaseEmbeddingModel(BaseModel, ABC): raise NotImplementedError def get_embeddings(self, input_text: str | List[str]): - """ - Retrieves embeddings for the input text. - - This function attempts to obtain embeddings for the given input text. It will retry a maximum number of times in case of failure. - - Parameters: - - input_text (str | List[str]): The input text, which can be a single string or a list of strings. - - Returns: - - embeddings: The embeddings for the input text. Returns None if the maximum number of retries is reached and no successful result is obtained. - """ - # Attempt to get embeddings, with a maximum number of retries set for i in range(self.max_retries): try: - # Attempt to get embeddings, return immediately if successful return self._get_embeddings(input_text) except Exception as e: - # Log exception information when an error occurs logger.exception(f"embedding model name={self.model_name} encounter error with e={e.args}") - - # If the maximum number of retries is reached and raise_exception is set to True, re-throw the exception if i == self.max_retries - 1 and self.raise_exception: raise e diff --git a/v1/embedding_model/openai_compatible_embedding_model.py b/v1/embedding_model/openai_compatible_embedding_model.py index f7897a12..2b892ff4 100644 --- a/v1/embedding_model/openai_compatible_embedding_model.py +++ b/v1/embedding_model/openai_compatible_embedding_model.py @@ -1,6 +1,7 @@ import os from typing import Literal, List +from dotenv import load_dotenv from openai import OpenAI from pydantic import Field, PrivateAttr, model_validator @@ -10,46 +11,19 @@ from v1.embedding_model.base_embedding_model import BaseEmbeddingModel @EMBEDDING_MODEL_REGISTRY.register("openai_compatible") class OpenAICompatibleEmbeddingModel(BaseEmbeddingModel): - api_key: str = Field(default_factory=lambda: os.getenv("OPENAI_API_KEY"), description="api key") - base_url: str = Field(default_factory=lambda: os.getenv("OPENAI_BASE_URL"), description="base url") - model_name: str = Field(default="text-embedding-v4", description="model name") + api_key: str = Field(default_factory=lambda: os.getenv("EMBEDDING_API_KEY"), description="api key") + base_url: str = Field(default_factory=lambda: os.getenv("EMBEDDING_BASE_URL"), description="base url") + model_name: str = Field(default="", description="model name") dimensions: int = Field(default=1024, description="dimensions") encoding_format: Literal["float", "base64"] = Field(default="float", description="encoding_format") _client: OpenAI = PrivateAttr() @model_validator(mode="after") def init_client(self): - """ - Initialize the OpenAI client after model validation. - - This method is called after the model's data has been validated, - ensuring that all necessary attributes are correctly set before - initializing the OpenAI client. It creates an instance of the OpenAI - client using the provided API key and base URL, storing it in the - - Returns: - self: Returns the instance of the current class for method chaining. - """ self._client = OpenAI(api_key=self.api_key, base_url=self.base_url) return self def _get_embeddings(self, input_text: str | List[str]): - """ - Generate embeddings for the input text. - - This method accepts either a single string or a list of strings as input, - and returns the corresponding embeddings based on the specified model. - - Parameters: - - input_text (str | List[str]): The input text, which can be a single string or a list of strings. - - Returns: - - List[float]: If the input is a single string, returns a list of floating-point numbers representing the embedding. - - List[List[float]]: If the input is a list of strings, returns a list where each item is a list of floating-point numbers representing the embedding of each string. - - Raises RuntimeError: If the input type is unsupported. - """ - - # Create embeddings using the specified model, input text, dimensions, and encoding format completion = self._client.embeddings.create( model=self.model_name, input=input_text, @@ -57,28 +31,21 @@ class OpenAICompatibleEmbeddingModel(BaseEmbeddingModel): encoding_format=self.encoding_format ) - # Determine the type of input and process accordingly if isinstance(input_text, str): - # If the input is a single string, return the embedding of that string return completion.data[0].embedding elif isinstance(input_text, list): - # If the input is a list of strings, initialize a list to hold the embeddings of each string result_emb = [[] for _ in range(len(input_text))] - # Iterate through the generated embeddings and assign them to the corresponding positions in the result list for emb in completion.data: result_emb[emb.index] = emb.embedding return result_emb else: - # If the input type is neither a string nor a list of strings, throw an exception raise RuntimeError(f"unsupported type={type(input_text)}") def main(): - from experiencemaker.utils.util_function import load_env_keys - load_env_keys() - + load_dotenv() model = OpenAICompatibleEmbeddingModel(dimensions=64, model_name="text-embedding-v4") res1 = model.get_embeddings( "The clothes are of good quality and look good, definitely worth the wait. I love them.") diff --git a/experiencemaker/enumeration/agent_state.py b/v1/enumeration/agent_state.py similarity index 100% rename from experiencemaker/enumeration/agent_state.py rename to v1/enumeration/agent_state.py diff --git a/v1/llm/base_llm.py b/v1/llm/base_llm.py index 805223d1..266827d3 100644 --- a/v1/llm/base_llm.py +++ b/v1/llm/base_llm.py @@ -2,9 +2,10 @@ import time from abc import ABC from typing import List, Literal -from loguru import logger, Message +from loguru import logger from pydantic import Field, BaseModel +from v1.schema.message import Message from v1.tool.base_tool import BaseTool @@ -31,21 +32,18 @@ class BaseLLM(BaseModel, ABC): def stream_print(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs): raise NotImplementedError - def _chat(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs) -> ActionMessage: + def _chat(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs) -> Message: raise NotImplementedError - def chat(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs) -> ActionMessage | None: + def chat(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs) -> Message | None: for i in range(self.max_retries): try: - # Attempt to execute the chat logic return self._chat(messages, tools, **kwargs) except Exception as e: - # Log exceptions during the chat process logger.exception(f"chat with model={self.model_name} encounter error with e={e.args}") time.sleep(1 + i) - # If the maximum number of retries is reached and raise_exception is set to True, then re-throw the exception if i == self.max_retries - 1 and self.raise_exception: raise e diff --git a/experiencemaker/model/openai_compatible_llm.py b/v1/llm/openai_compatible_llm.py similarity index 77% rename from experiencemaker/model/openai_compatible_llm.py rename to v1/llm/openai_compatible_llm.py index 93b917cc..2256140b 100644 --- a/experiencemaker/model/openai_compatible_llm.py +++ b/v1/llm/openai_compatible_llm.py @@ -1,37 +1,28 @@ import os from typing import List +from dotenv import load_dotenv from loguru import logger from openai import OpenAI from openai.types import CompletionUsage from pydantic import Field, PrivateAttr, model_validator -from experiencemaker.enumeration.chunk_enum import ChunkEnum -from experiencemaker.model.base_llm import BaseLLM, LLM_REGISTRY -from experiencemaker.schema.trajectory import Message, ActionMessage, ToolCall -from experiencemaker.tool.base_tool import BaseTool +from v1.enumeration.chunk_enum import ChunkEnum +from v1.llm import LLM_REGISTRY +from v1.llm.base_llm import BaseLLM +from v1.schema.message import Message, ToolCall +from v1.tool.base_tool import BaseTool @LLM_REGISTRY.register("openai_compatible") class OpenAICompatibleBaseLLM(BaseLLM): - model_name: str = Field(default="qwen3-32b") - api_key: str = Field(default_factory=lambda: os.getenv("OPENAI_API_KEY"), description="api key") - base_url: str = Field(default_factory=lambda: os.getenv("OPENAI_BASE_URL"), description="base url") + model_name: str = Field(default="") + api_key: str = Field(default_factory=lambda: os.getenv("LLM_API_KEY"), description="api key") + base_url: str = Field(default_factory=lambda: os.getenv("LLM_BASE_URL"), description="base url") _client: OpenAI = PrivateAttr() @model_validator(mode="after") def init_client(self): - """ - Initialize the OpenAI client after model validation. - - This method is called after the model's data has been validated, - ensuring that all necessary attributes are correctly set before - initializing the OpenAI client. It creates an instance of the OpenAI - client using the provided API key and base URL, storing it in the - - Returns: - self: Returns the instance of the current class for method chaining. - """ self._client = OpenAI(api_key=self.api_key, base_url=self.base_url) return self @@ -40,14 +31,14 @@ class OpenAICompatibleBaseLLM(BaseLLM): try: completion = self._client.chat.completions.create( model=self.model_name, - messages=[x.simple_dict for x in messages], + messages=[x.simple_dump() for x in messages], seed=self.seed, top_p=self.top_p, stream=True, stream_options=self.stream_options, temperature=self.temperature, extra_body={"enable_thinking": self.enable_thinking}, - tools=[x.simple_dict for x in tools] if tools else None, + tools=[x.simple_dump() for x in tools] if tools else None, tool_choice=self.tool_choice, parallel_tool_calls=self.parallel_tool_calls) @@ -103,7 +94,7 @@ class OpenAICompatibleBaseLLM(BaseLLM): else: yield e.args, ChunkEnum.ERROR - def _chat(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs) -> ActionMessage: + def _chat(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs) -> Message: reasoning_content = "" answer_content = "" tool_calls = [] @@ -118,9 +109,7 @@ class OpenAICompatibleBaseLLM(BaseLLM): elif chunk_enum is ChunkEnum.TOOL: tool_calls.append(chunk) - return ActionMessage(reasoning_content=reasoning_content, - content=answer_content, - tool_calls=tool_calls) + return Message(reasoning_content=reasoning_content, content=answer_content, tool_calls=tool_calls) def stream_print(self, messages: List[Message], tools: List[BaseTool] = None, **kwargs): enter_think = False @@ -154,14 +143,12 @@ class OpenAICompatibleBaseLLM(BaseLLM): def main(): - from experiencemaker.utils.util_function import load_env_keys - from experiencemaker.tool.dashscope_search_tool import DashscopeSearchTool - from experiencemaker.tool.code_tool import CodeTool - from experiencemaker.enumeration.role import Role + from v1.tool.dashscope_search_tool import DashscopeSearchTool + from v1.tool.code_tool import CodeTool + from v1.enumeration.role import Role - load_env_keys() + load_dotenv() model_name = "qwen-max-2025-01-25" - # model_name = "qwen3-32b" llm = OpenAICompatibleBaseLLM(model_name=model_name) tools: List[BaseTool] = [DashscopeSearchTool(), CodeTool()] diff --git a/v1/op/base_op.py b/v1/op/base_op.py index ca6afc27..72edb05b 100644 --- a/v1/op/base_op.py +++ b/v1/op/base_op.py @@ -1,12 +1,13 @@ -from abc import abstractmethod, ABC +from abc import abstractmethod, ABCMeta from loguru import logger +from v1.op.prompt_mixin import PromptMixin from v1.pipeline.pipeline_context import PipelineContext from v1.utils.timer import Timer -class BaseOp(ABC): +class BaseOp(PromptMixin, metaclass=ABCMeta): def __init__(self, context: PipelineContext, **kwargs): super().__init__(**kwargs) diff --git a/v1/op/prompt_mixin.py b/v1/op/prompt_mixin.py new file mode 100644 index 00000000..bb3d7f0b --- /dev/null +++ b/v1/op/prompt_mixin.py @@ -0,0 +1,66 @@ +from pathlib import Path + +import yaml +from loguru import logger + + +class PromptMixin: + + def __init__(self, file_path: Path | str = None, prompt_dict: dict = None): + self.prompt_dict: dict = {} + self.prompt_dict.update(**self.load_prompt_by_file_path(file_path)) + if prompt_dict: + for key, value in prompt_dict.items(): + if isinstance(value, str): + self.prompt_dict[key] = value + logger.info(f"add prompt_dict key={key}") + + @staticmethod + def load_prompt_by_file_path(file_path: Path | str = None): + prompt_dict = {} + if file_path is None: + return prompt_dict + + if isinstance(file_path, str): + file_path = Path(file_path) + + if not file_path.exists(): + return prompt_dict + + with file_path.open() as f: + prompt_dict = yaml.load(f, yaml.FullLoader) + logger.info(f"add prompt_dict keys={prompt_dict.keys()}") + return prompt_dict + + def prompt_format(self, prompt_name: str, **kwargs): + prompt = self.prompt_dict[prompt_name] + + flag_kwargs = {k: v for k, v in kwargs.items() if isinstance(v, bool)} + other_kwargs = {k: v for k, v in kwargs.items() if not isinstance(v, bool)} + + if flag_kwargs: + split_prompt = [] + for line in prompt.strip().split("\n"): + hit = False + hit_flag = True + for key, flag in kwargs.items(): + if not line.startswith(f"[{key}]"): + continue + + else: + hit = True + hit_flag = flag + line = line.strip(f"[{key}]") + break + + if not hit: + split_prompt.append(line) + elif hit_flag: + split_prompt.append(line) + + prompt = "\n".join(split_prompt) + + if other_kwargs: + prompt = prompt.format(**other_kwargs) + + return prompt diff --git a/v1/service/experience_maker_client.py b/v1/service/experience_maker_client.py new file mode 100644 index 00000000..0b22bce1 --- /dev/null +++ b/v1/service/experience_maker_client.py @@ -0,0 +1,31 @@ +from pydantic import Field + +from v1.schema.request import RetrieverRequest, SummarizerRequest, VectorStoreRequest, AgentRequest +from v1.schema.response import RetrieverResponse, SummarizerResponse, VectorStoreResponse, AgentResponse +from v1.utils.http_client import HttpClient + + +class ExperienceMakerClient(HttpClient): + base_url: str = Field(default=...) + + def call_retriever(self, request: RetrieverRequest): + self.url = self.base_url + "/retriever" + return RetrieverResponse(**self.request(json_data=request.model_dump())) + + def call_summarizer(self, request: SummarizerRequest): + self.url = self.base_url + "/summarizer" + return SummarizerResponse(**self.request(json_data=request.model_dump())) + + def call_vector_store(self, request: VectorStoreRequest): + self.url = self.base_url + "/vector_store" + return VectorStoreResponse(**self.request(json_data=request.model_dump())) + + def call_agent(self, request: AgentRequest): + self.url = self.base_url + "/agent" + return AgentResponse(**self.request(json_data=request.model_dump())) + + +if __name__ == "__main__": + client = ExperienceMakerClient(base_url="http://0.0.0.0:8001") + response = client.call_retriever(RetrieverRequest(workspace_id="123", query="hello world")) + print(response.model_dump()) diff --git a/v1/service/experience_maker_service.py b/v1/service/experience_maker_service.py index 22ae644e..78ae1e6c 100644 --- a/v1/service/experience_maker_service.py +++ b/v1/service/experience_maker_service.py @@ -19,7 +19,7 @@ class ExperienceMakerService: self.init_app_config: AppConfig = self.config_parser.get_app_config() self.thread_pool = ThreadPoolExecutor(max_workers=self.init_app_config.thread_pool.max_workers) - # The vectorstore is initialized at the very beginning and then used directly afterwards. + # The vectorstore is initialized at the very beginning and then used directly afterward. self.vector_store_dict: dict = {} for name, config in self.init_app_config.vector_store.items(): assert config.backend in VECTOR_STORE_REGISTRY, f"backend={config.backend} is not existed" diff --git a/experiencemaker/tool/code_tool.py b/v1/tool/code_tool.py similarity index 95% rename from experiencemaker/tool/code_tool.py rename to v1/tool/code_tool.py index 8b61b85f..9f0fcbea 100644 --- a/experiencemaker/tool/code_tool.py +++ b/v1/tool/code_tool.py @@ -1,7 +1,7 @@ import sys from io import StringIO -from experiencemaker.tool.base_tool import BaseTool +from v1.tool.base_tool import BaseTool class CodeTool(BaseTool): diff --git a/experiencemaker/tool/dashscope_search_tool.py b/v1/tool/dashscope_search_tool.py similarity index 97% rename from experiencemaker/tool/dashscope_search_tool.py rename to v1/tool/dashscope_search_tool.py index 46e5a320..4f455a6c 100644 --- a/experiencemaker/tool/dashscope_search_tool.py +++ b/v1/tool/dashscope_search_tool.py @@ -3,10 +3,11 @@ from typing import Literal import dashscope from dashscope.api_entities.dashscope_response import Message +from dotenv import load_dotenv from loguru import logger from pydantic import Field -from experiencemaker.tool.base_tool import BaseTool +from v1.tool.base_tool import BaseTool class DashscopeSearchTool(BaseTool): @@ -142,8 +143,7 @@ Extract the original content related to the user's question directly from the co def main(): - from experiencemaker.utils.util_function import load_env_keys - load_env_keys() + load_dotenv() query = "What is artificial intelligence?" tool = DashscopeSearchTool(stream_print=True) diff --git a/experiencemaker/tool/mcp_tool.py b/v1/tool/mcp_tool.py similarity index 98% rename from experiencemaker/tool/mcp_tool.py rename to v1/tool/mcp_tool.py index 21315da4..53cef3cd 100644 --- a/experiencemaker/tool/mcp_tool.py +++ b/v1/tool/mcp_tool.py @@ -5,7 +5,7 @@ from mcp import ClientSession from mcp.client.sse import sse_client from pydantic import Field, model_validator -from experiencemaker.tool.base_tool import BaseTool +from v1.tool.base_tool import BaseTool class MCPTool(BaseTool): diff --git a/experiencemaker/tool/terminate_tool.py b/v1/tool/terminate_tool.py similarity index 93% rename from experiencemaker/tool/terminate_tool.py rename to v1/tool/terminate_tool.py index fb2412e7..ee3053a3 100644 --- a/experiencemaker/tool/terminate_tool.py +++ b/v1/tool/terminate_tool.py @@ -1,4 +1,4 @@ -from experiencemaker.tool.base_tool import BaseTool +from v1.tool.base_tool import BaseTool class TerminateTool(BaseTool): diff --git a/experiencemaker/utils/file_handler.py b/v1/utils/file_handler.py similarity index 68% rename from experiencemaker/utils/file_handler.py rename to v1/utils/file_handler.py index 8ca8ac5c..3e680907 100644 --- a/experiencemaker/utils/file_handler.py +++ b/v1/utils/file_handler.py @@ -1,17 +1,13 @@ import json from pathlib import Path -from typing import Any import yaml -from pydantic import BaseModel, Field, PrivateAttr -class FileHandler(BaseModel): - file_path: str | Path = Field(default=...) - _obj: Any = PrivateAttr() +class FileHandler: - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, file_path: str | Path): + self.file_path: Path = Path(file_path) suffix = Path(self.file_path).suffix if suffix == ".json": self._obj = json diff --git a/experiencemaker/utils/http_client.py b/v1/utils/http_client.py similarity index 99% rename from experiencemaker/utils/http_client.py rename to v1/utils/http_client.py index 3f12e308..38e76782 100644 --- a/experiencemaker/utils/http_client.py +++ b/v1/utils/http_client.py @@ -6,7 +6,7 @@ import requests from loguru import logger from pydantic import BaseModel, Field, PrivateAttr, model_validator -from experiencemaker.enumeration.http_enum import HttpEnum +from v1.enumeration.http_enum import HttpEnum class HttpClient(BaseModel): diff --git a/v1/utils/timer.py b/v1/utils/timer.py index 5daa87ce..74f8a3f9 100644 --- a/v1/utils/timer.py +++ b/v1/utils/timer.py @@ -1,5 +1,4 @@ import time -from typing import Optional from loguru import logger @@ -16,7 +15,7 @@ class Timer(object): def __enter__(self, *args, **kwargs): self.time_start = time.time() - logger.info(f"========== {self.name} start ==========", stacklevel=self.stack_level) + logger.info(f"---------- enter {self.name} ----------", stacklevel=self.stack_level) return self def __exit__(self, *args): @@ -27,7 +26,7 @@ class Timer(object): else: time_str = f"{self.time_cost:.3f}s" - logger.info(f"========== {self.name} end, time_cost={time_str} ==========", stacklevel=self.stack_level) + logger.info(f"---------- leave {self.name} [{time_str}] ----------", stacklevel=self.stack_level) def timer(name: str = None, use_ms: bool = False, stack_level: int = 2):