mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-09-06 08:16:00 +00:00
move code
This commit is contained in:
parent
d82af71fed
commit
f377d3a8a9
29 changed files with 139 additions and 866 deletions
|
|
@ -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"}))
|
||||
|
|
@ -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"}'
|
||||
"""
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class ChunkEnum(str, Enum):
|
||||
THINK = "think"
|
||||
ANSWER = "answer"
|
||||
TOOL = "tool"
|
||||
USAGE = "usage"
|
||||
ERROR = "error"
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class HttpEnum(str, Enum):
|
||||
GET = "get"
|
||||
POST = "post"
|
||||
|
|
@ -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
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
from experiencemaker.model.openai_compatible_embedding_model import OpenAICompatibleEmbeddingModel
|
||||
from experiencemaker.model.openai_compatible_llm import OpenAICompatibleBaseLLM
|
||||
|
|
@ -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)}")
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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())
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()]
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
66
v1/op/prompt_mixin.py
Normal file
66
v1/op/prompt_mixin.py
Normal file
|
|
@ -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
|
||||
31
v1/service/experience_maker_client.py
Normal file
31
v1/service/experience_maker_client.py
Normal file
|
|
@ -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())
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
@ -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)
|
||||
|
|
@ -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):
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from experiencemaker.tool.base_tool import BaseTool
|
||||
from v1.tool.base_tool import BaseTool
|
||||
|
||||
|
||||
class TerminateTool(BaseTool):
|
||||
|
|
@ -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
|
||||
|
|
@ -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):
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue