From 043aea6cca895d88daa1ef4e5033b6eb4164985b Mon Sep 17 00:00:00 2001 From: wanyaokun <12345678> Date: Thu, 22 Aug 2024 11:06:22 +0800 Subject: [PATCH 01/15] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=85=B3=E9=94=AE=E8=AF=8D=E6=A3=80=E7=B4=A2=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/engine/__init__.py | 14 ++++++++------ backend/app/engine/generate.py | 9 +++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/backend/app/engine/__init__.py b/backend/app/engine/__init__.py index fc36c14..12a45fc 100644 --- a/backend/app/engine/__init__.py +++ b/backend/app/engine/__init__.py @@ -11,6 +11,7 @@ from sqlalchemy import create_engine, Engine from app.engine.loaders.db import makeDescriptionByEngine from app.engine.tools import ToolFactory from app.engine.index import get_index +from app.engine.retriever.CHBM25Retriever import CHBM25Retriever from app.settings import get_node_postprocessors from llama_index.core.retrievers import BaseRetriever @@ -29,9 +30,6 @@ class HybridRetriever(BaseRetriever): filters = None, **kwargs: Any, ) -> None: - from llama_index.retrievers.bm25 import BM25Retriever - from nltk.corpus import stopwords - super().__init__(**kwargs) self._vector_index = vector_index self._embed_model = vector_index._embed_model @@ -39,9 +37,13 @@ class HybridRetriever(BaseRetriever): self._vecRetriever = vector_index.as_retriever( similarity_top_k=similarity_top_k,filters = filters ) - self._bm25Retriever = BM25Retriever.from_defaults(similarity_top_k=similarity_top_k, - nodes=self._vector_index.vector_store.get_nodes(None), - language=stopwords.words('chinese')) + + STORAGE_DIR = os.getenv("BM_RETRIEVER_PATH", "storage_bm") + if os.path.exists(STORAGE_DIR) and len(os.listdir(STORAGE_DIR)) > 0: + self._bm25Retriever = CHBM25Retriever.from_persist_dir(STORAGE_DIR) + else: + bmRetriver = CHBM25Retriever.from_defaults(similarity_top_k=similarity_top_k,nodes=self._vector_index.vector_store.get_nodes(None)) + bmRetriver.persist(STORAGE_DIR) self._alpha = alpha def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]: diff --git a/backend/app/engine/generate.py b/backend/app/engine/generate.py index 115c175..87ecfa1 100644 --- a/backend/app/engine/generate.py +++ b/backend/app/engine/generate.py @@ -8,6 +8,7 @@ import os from app.engine.loaders import get_documents from app.engine.vectordb import get_vector_store from app.settings import init_settings +from app.engine.retriever.CHBM25Retriever import CHBM25Retriever from llama_index.core.ingestion import IngestionPipeline from llama_index.core.node_parser import SentenceSplitter from llama_index.core.settings import Settings @@ -58,6 +59,13 @@ def persist_storage(docstore, vector_store): storage_context.persist(STORAGE_DIR) +def persist_BMRetriever(vector_store): + STORAGE_DIR = os.getenv("BM_RETRIEVER_PATH", "storage_bm") + top_k = int(os.getenv("TOP_K", "3")) + bmRetriver = CHBM25Retriever.from_defaults(similarity_top_k=top_k,nodes=vector_store.get_nodes([])) + bmRetriver.persist(STORAGE_DIR) + + def generate_datasource(): init_settings() logger.info("Generate index for the provided data") @@ -75,6 +83,7 @@ def generate_datasource(): # Build the index and persist storage persist_storage(docstore, vector_store) + persist_BMRetriever(vector_store) logger.info("Finished generating the index") From 8d7190d0b6d0451b964a84ff9eab56d51989da63 Mon Sep 17 00:00:00 2001 From: wanyaokun <12345678> Date: Thu, 22 Aug 2024 11:07:23 +0800 Subject: [PATCH 02/15] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E5=AD=97=E6=A3=80=E7=B4=A2=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/engine/retriever/CHBM25Retriever.py | 133 ++++++++++++++++++ backend/app/engine/retriever/CHTokener.py | 46 ++++++ 2 files changed, 179 insertions(+) create mode 100644 backend/app/engine/retriever/CHBM25Retriever.py create mode 100644 backend/app/engine/retriever/CHTokener.py diff --git a/backend/app/engine/retriever/CHBM25Retriever.py b/backend/app/engine/retriever/CHBM25Retriever.py new file mode 100644 index 0000000..fa5d5ec --- /dev/null +++ b/backend/app/engine/retriever/CHBM25Retriever.py @@ -0,0 +1,133 @@ +import json +import logging +import os + +from typing import Any, Callable, Dict, List, Optional, cast + +from llama_index.core.base.base_retriever import BaseRetriever +from llama_index.core.callbacks.base import CallbackManager +from llama_index.core.constants import DEFAULT_SIMILARITY_TOP_K +from llama_index.core.indices.vector_store.base import VectorStoreIndex +from llama_index.core.schema import BaseNode, IndexNode, NodeWithScore, QueryBundle +from llama_index.core.storage.docstore.types import BaseDocumentStore +from llama_index.core.vector_stores.utils import ( + node_to_metadata_dict, + metadata_dict_to_node, +) + +import bm25s +from app.engine.retriever.CHTokener import chTokenize + +CHDEFAULT_PERSIST_ARGS = {"similarity_top_k": "similarity_top_k", "_verbose": "verbose"} + +CHDEFAULT_PERSIST_FILENAME = "retriever.json" + +class CHBM25Retriever(BaseRetriever): + def __init__( + self, + nodes: Optional[List[BaseNode]] = None, + existing_bm25: Optional[bm25s.BM25] = None, + similarity_top_k: int = DEFAULT_SIMILARITY_TOP_K, + callback_manager: Optional[CallbackManager] = None, + objects: Optional[List[IndexNode]] = None, + object_map: Optional[dict] = None, + verbose: bool = False, + ) -> None: + self.similarity_top_k = similarity_top_k + if existing_bm25 is not None: + self.bm25 = existing_bm25 + self.corpus = existing_bm25.corpus + else: + from nltk.corpus import stopwords + if nodes is None: + raise ValueError("Please pass nodes or an existing BM25 object.") + + self.corpus = [node_to_metadata_dict(node) for node in nodes] + + corpus_tokens = chTokenize( + [node.get_content() for node in nodes], + show_progress=verbose, + ) + self.bm25 = bm25s.BM25() + self.bm25.index(corpus_tokens, show_progress=verbose) + super().__init__( + callback_manager=callback_manager, + object_map=object_map, + objects=objects, + verbose=verbose, + ) + + @classmethod + def from_defaults( + cls, + index: Optional[VectorStoreIndex] = None, + nodes: Optional[List[BaseNode]] = None, + docstore: Optional[BaseDocumentStore] = None, + similarity_top_k: int = DEFAULT_SIMILARITY_TOP_K, + verbose: bool = False, + ) -> "CHBM25Retriever": + if sum(bool(val) for val in [index, nodes, docstore]) != 1: + raise ValueError("Please pass exactly one of index, nodes, or docstore.") + + if index is not None: + docstore = index.docstore + + if docstore is not None: + nodes = cast(List[BaseNode], list(docstore.docs.values())) + + assert ( + nodes is not None + ), "Please pass exactly one of index, nodes, or docstore." + + return cls( + nodes=nodes, + similarity_top_k=similarity_top_k, + verbose=verbose, + ) + + def get_persist_args(self) -> Dict[str, Any]: + """Get Persist Args Dict to Save.""" + return { + CHDEFAULT_PERSIST_ARGS[key]: getattr(self, key) + for key in CHDEFAULT_PERSIST_ARGS + if hasattr(self, key) + } + + def persist(self, path: str, **kwargs: Any) -> None: + """Persist the retriever to a directory.""" + self.bm25.save(path, corpus=self.corpus, **kwargs) + with open(os.path.join(path, CHDEFAULT_PERSIST_FILENAME), "w") as f: + json.dump(self.get_persist_args(), f, indent=2) + + @classmethod + def from_persist_dir(cls, path: str, **kwargs: Any) -> "CHBM25Retriever": + """Load the retriever from a directory.""" + bm25 = bm25s.BM25.load(path, load_corpus=True, **kwargs) + with open(os.path.join(path, CHDEFAULT_PERSIST_FILENAME)) as f: + retriever_data = json.load(f) + return cls(existing_bm25=bm25, **retriever_data) + + def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]: + query = query_bundle.query_str + tokenized_query = chTokenize( + query,show_progress=self._verbose + ) + indexes, scores = self.bm25.retrieve( + tokenized_query, k=self.similarity_top_k, show_progress=self._verbose + ) + + # batched, but only one query + indexes = indexes[0] + scores = scores[0] + + nodes: List[NodeWithScore] = [] + for idx, score in zip(indexes, scores): + # idx can be an int or a dict of the node + if isinstance(idx, dict): + node = metadata_dict_to_node(idx) + else: + node_dict = self.corpus[int(idx)] + node = metadata_dict_to_node(node_dict) + nodes.append(NodeWithScore(node=node, score=float(score))) + + return nodes \ No newline at end of file diff --git a/backend/app/engine/retriever/CHTokener.py b/backend/app/engine/retriever/CHTokener.py new file mode 100644 index 0000000..9c5a071 --- /dev/null +++ b/backend/app/engine/retriever/CHTokener.py @@ -0,0 +1,46 @@ +from typing import Any, Dict, List, Union, Callable, NamedTuple +from bm25s.tokenization import * + +try: + from tqdm.auto import tqdm +except ImportError: + + def tqdm(iterable, *args, **kwargs): + return iterable + + +def chinese_tokenizer(text: str) -> List[str]: + import jieba + from nltk.corpus import stopwords + tokens = jieba.lcut(text) + return [token for token in tokens if token not in stopwords.words('chinese')] + +def chTokenize( + texts, + show_progress: bool = True, + leave: bool = False, +) -> Union[List[List[str]], Tokenized]: + if isinstance(texts, str): + texts = [texts] + + corpus_ids = [] + token_to_index = {} + + for text in tqdm( + texts, desc="Split strings", leave=leave, disable=not show_progress + ): + + splitted = chinese_tokenizer(text) + doc_ids = [] + + for token in splitted: + if token not in token_to_index: + token_to_index[token] = len(token_to_index) + + token_id = token_to_index[token] + doc_ids.append(token_id) + + corpus_ids.append(doc_ids) + + return Tokenized(ids=corpus_ids, vocab=token_to_index) + From 586bb76c9c51183db8a1331cfe851e1afbebd54f Mon Sep 17 00:00:00 2001 From: wanyaokun <12345678> Date: Thu, 22 Aug 2024 11:09:16 +0800 Subject: [PATCH 03/15] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E5=AD=97=E6=A3=80=E7=B4=A2=E7=BC=93=E5=AD=98=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/.env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/.env.example b/backend/.env.example index 2b1db19..37ba235 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -49,6 +49,7 @@ VECTOR_STORE_COLLECTION=default # Specify this if you are using a local vector database. # Otherwise, use VECTOR_STORE__HOST and VECTOR_STORE__PORT config above VECTOR_STORE_PATH=./storage_vector +BM_RETRIEVER_PATH =./storage_bm From 3460b8410e8e4b647c31c0d0bea67a52e057b537 Mon Sep 17 00:00:00 2001 From: wanyaokun <12345678> Date: Thu, 22 Aug 2024 12:06:43 +0800 Subject: [PATCH 04/15] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E5=AD=97=E7=BC=93=E5=AD=98=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/.env.xinference | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/.env.xinference b/backend/.env.xinference index ae37317..48c95de 100644 --- a/backend/.env.xinference +++ b/backend/.env.xinference @@ -9,9 +9,9 @@ TOP_K=10 # 是否启用检索重排功能 RERANK_ENABLED=true # 是否启用混合检索 -HYBRID_ENABLED = false +HYBRID_ENABLED = true # 混合检索阈值 -HYBRID_ALPHA = 0.5 +HYBRID_ALPHA = 0.6 # Rerank model RERANK_MODEL=bge-reranker-v2-m3 RERANK_BASE_URL=http://10.1.16.39:9995 @@ -80,7 +80,7 @@ VECTOR_STORE_COLLECTION=default # Specify this if you are using a local vector database. # Otherwise, use VECTOR_STORE__HOST and VECTOR_STORE__PORT config above VECTOR_STORE_PATH=./storage_vector - +BM_RETRIEVER_PATH =./storage_bm PHOENIX_API_KEY=123456 From 870af69189e65ed86488519d1c1783da2f6ef8dd Mon Sep 17 00:00:00 2001 From: wanyaokun <12345678> Date: Thu, 22 Aug 2024 12:09:15 +0800 Subject: [PATCH 05/15] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8C=85=E4=BE=9D?= =?UTF-8?q?=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index bb8fcf2..de1fbbb 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -18,6 +18,7 @@ llama-index = "0.10.63" cachetools = "^5.3.3" protobuf = "4.25.4" nltk = "^3.8.2" +jieba = "^0.42.1" #arize-phoenix = "^4.12.0" openinference-instrumentation-llama-index="2.2.3" From db006985d75de1609007dc6488e6a21a47651bd5 Mon Sep 17 00:00:00 2001 From: chentianrui Date: Thu, 22 Aug 2024 15:24:29 +0800 Subject: [PATCH 06/15] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E8=AF=8D=EF=BC=8C=E7=BA=A6=E6=9D=9F=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=9B=9E=E7=AD=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/engine/__init__.py | 114 +++++++++------------------------ backend/main.py | 83 +++++++++++++++++++++++- 2 files changed, 113 insertions(+), 84 deletions(-) diff --git a/backend/app/engine/__init__.py b/backend/app/engine/__init__.py index 12a45fc..7b6a111 100644 --- a/backend/app/engine/__init__.py +++ b/backend/app/engine/__init__.py @@ -4,91 +4,25 @@ from llama_index.core import SQLDatabase, SummaryIndex, VectorStoreIndex from llama_index.core.indices.struct_store import SQLTableRetrieverQueryEngine from llama_index.core.objects import SQLTableNodeMapping, ObjectIndex from llama_index.core.settings import Settings -from llama_index.core.agent import AgentRunner, StructuredPlannerAgent, FunctionCallingAgentWorker +from llama_index.core.agent import AgentRunner, StructuredPlannerAgent, FunctionCallingAgentWorker, ReActChatFormatter from llama_index.core.tools.query_engine import QueryEngineTool from sqlalchemy import create_engine, Engine +from llama_index.core.response_synthesizers.type import ResponseMode from app.engine.loaders.db import makeDescriptionByEngine from app.engine.tools import ToolFactory from app.engine.index import get_index -from app.engine.retriever.CHBM25Retriever import CHBM25Retriever from app.settings import get_node_postprocessors -from llama_index.core.retrievers import BaseRetriever -from llama_index.core import QueryBundle -from llama_index.core.schema import NodeWithScore -from typing import List, Any, Optional,Dict -from llama_index.core.query_engine.retriever_query_engine import RetrieverQueryEngine +import nest_asyncio -class HybridRetriever(BaseRetriever): - def __init__( - self, - vector_index, - similarity_top_k: int = 2, - out_top_k: Optional[int] = None, - alpha: float = 0.5, - filters = None, - **kwargs: Any, - ) -> None: - super().__init__(**kwargs) - self._vector_index = vector_index - self._embed_model = vector_index._embed_model - self._out_top_k = out_top_k or similarity_top_k - self._vecRetriever = vector_index.as_retriever( - similarity_top_k=similarity_top_k,filters = filters - ) - - STORAGE_DIR = os.getenv("BM_RETRIEVER_PATH", "storage_bm") - if os.path.exists(STORAGE_DIR) and len(os.listdir(STORAGE_DIR)) > 0: - self._bm25Retriever = CHBM25Retriever.from_persist_dir(STORAGE_DIR) - else: - bmRetriver = CHBM25Retriever.from_defaults(similarity_top_k=similarity_top_k,nodes=self._vector_index.vector_store.get_nodes(None)) - bmRetriver.persist(STORAGE_DIR) - self._alpha = alpha - - def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]: - vecNodes:List[NodeWithScore] = self._vecRetriever.retrieve(query_bundle.query_str) - bmNodes:List[NodeWithScore] = self._bm25Retriever.retrieve(query_bundle.query_str) - - bmDic:Dict[str,NodeWithScore] = {} - for node in bmNodes: - bmDic[node.node_id] = node - - result_tups = [] - for i in range(len(vecNodes)): - node = vecNodes[i] - bmScore = 0.0 - if node.node_id in bmDic: - bmScore = bmDic[node.node_id].score - bmDic.pop(node.node_id) - else: - bmScore = 0.0 - full_similarity = (self._alpha * node.score) + ( - (1 - self._alpha) * bmScore - ) - result_tups.append((full_similarity, node)) - - for _,node in bmDic.items(): - full_similarity = (1 - self._alpha) * node.score - result_tups.append((full_similarity, node)) - - result_tups = sorted(result_tups, key=lambda x: x[0], reverse=True) - for full_score, node in result_tups: - node.score = full_score - return [n for _, n in result_tups][:self._out_top_k] - -def get_Retriever(index,**kwargs): - bEnableHybrid = True if os.getenv("HYBRID_ENABLED",False).title() == 'True' else False - if bEnableHybrid: - alpha = float(os.getenv("HYBRID_ALPHA", "0.5")) - retriever = HybridRetriever(index,alpha = alpha,**kwargs) - else: - retriever = index.as_retriever(**kwargs) - return retriever +nest_asyncio.apply() sql_database = None sql_obj_index = None + + def get_chat_engine(filters=None, params=None): system_prompt = os.getenv("SYSTEM_PROMPT") top_k = int(os.getenv("TOP_K", "3")) @@ -128,12 +62,13 @@ def get_chat_engine(filters=None, params=None): # 创建向量检索查询工具 postprocess = get_node_postprocessors() - query_engine = RetrieverQueryEngine.from_args( - get_Retriever(index,similarity_top_k=top_k, - filters=filters), + query_engine = index.as_query_engine( + similarity_top_k=top_k, filters=filters, node_postprocessors=postprocess, + use_async=True, + streaming=True, +# response_mode=ResponseMode.TREE_SUMMARIZE, ) - query_engine_tool = QueryEngineTool.from_defaults(query_engine=query_engine, name="zj_query_tool", description="由博微公司编制的关于电力造价知识、电力造价编制软件知识和造价工程文件结构的知识库。适用于查询电力领域、电力造价领域、博微、博微电力、博微造价等业务等内容。如果本知识库没有直接答案但有解决思路的可以返回解决办法后建议使用“zjdata_query_tool”工具。如果你不知道答案,就说你不知道,不要编造答案。", ) @@ -145,12 +80,31 @@ def get_chat_engine(filters=None, params=None): # Add additional tools tools += ToolFactory.from_env() - return AgentRunner.from_llm( + # agentrunner = StructuredPlannerAgent.from_llm( + # llm=Settings.llm, + # tools=tools, + # system_prompt=system_prompt, + # verbose=True, + # ) + + # prompts = agentrunner.agent_worker._get_prompts() + # prompts["system_prompt"].template = """您的设计旨在帮助完成各种任务,从回答问题到提供其他类型分析的摘要。\n\n##工具\n\n你可以访问各种工具。你有责任按照你认为合适的顺序使用这些工具来完成当前的任务。\n这可能需要将任务分解为子任务,并使用不同的工具来完成每个子任务。\n\n你可以访问以下工具:\n{tool_desc}\n\n\n##输出格式\n\n请用与问题相同的语言回答,并使用以下格式:\n\n```\nThought: 用户当前的语言是:(user's language)。我需要使用工具来帮助我回答问题。\nAction: 如果使用工具,则为工具名称(one of {tool_names})。\nAction Input: 输入给工具的内容,使用JSON格式表示kwargs(例如{{\"input\": \"hello world\", \"num_beams\": 5}})\n```\n\n请始终以Thought开始。\n\n切勿用Markdown代码标记包围你的响应。如果需要,可以在响应中使用代码标记。\n\n请为Action Input使用有效的JSON格式。不要这样做{{\'input\': \'hello world\', \'num_beams\': 5}}。\n\n如果使用此格式,用户将以下面的格式进行回应:\n\n```\nObservation: 工具响应\n```\n\n你应该继续重复上述格式,直到你有足够的信息来回答问题而无需使用更多工具。此时,你必须使用以下两种格式之一进行回答:\n\n```\nThought: 我可以不用任何工具来回答。我将使用用户的语言来回答。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n```\nThought: 我无法使用提供的工具回答问题。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n## 当前对话\n\n以下是当前对话,由人类和助手的消息交替组成。\n""" + # agentrunner.agent_worker.update_prompts(prompts) + + prefix_messages = ("""您的设计旨在帮助完成各种任务,从回答问题到提供其他类型分析的摘要。\n\n##工具\n\n你可以访问各种工具。你有责任按照你认为合适的顺序使用这些工具来完成当前的任务。\n这可能需要将任务分解为子任务,并使用不同的工具来完成每个子任务。\n\n你可以访问以下工具:\n{tool_desc}\n\n\n##输出格式\n\n请用与问题相同的语言回答,并使用以下格式:\n\n```\nThought: 用户当前的语言是:(user's language)。我需要使用工具来帮助我回答问题。\nAction: 如果使用工具,则为工具名称(one of {tool_names})。\nAction Input: 输入给工具的内容,使用JSON格式表示kwargs(例如{{\"input\": \"hello world\", \"num_beams\": 5}})\n```\n\n请始终以Thought开始。\n\n切勿用Markdown代码标记包围你的响应。如果需要,可以在响应中使用代码标记。\n\n请为Action Input使用有效的JSON格式。不要这样做{{\'input\': \'hello world\', \'num_beams\': 5}}。\n\n如果使用此格式,用户将以下面的格式进行回应:\n\n```\nObservation: 工具响应\n```\n\n你应该继续重复上述格式,直到你有足够的信息来回答问题而无需使用更多工具。此时,你必须使用以下两种格式之一进行回答:\n\n```\nThought: 我可以不用任何工具来回答。我将使用用户的语言来回答。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n```\nThought: 我无法使用提供的工具回答问题。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n##如果从工具中得到的回应是Empty Response,那么只需要回答“我不知道”,不需要额外回答别的内容。## 当前对话\n\n以下是当前对话,由人类和助手的消息交替组成。\n""") + + react_chat_formatter = ReActChatFormatter.from_defaults(prefix_messages) + + agentrunner = AgentRunner.from_llm( llm=Settings.llm, tools=tools, + react_chat_formatter=react_chat_formatter, system_prompt=system_prompt, verbose=True, ) + return agentrunner + + # create the function calling worker for reasoning # worker = FunctionCallingAgentWorker.from_tools( # tools, verbose=True @@ -158,9 +112,3 @@ def get_chat_engine(filters=None, params=None): # # # wrap the worker in the top-level planner # return StructuredPlannerAgent(worker, tools) - - - - - - diff --git a/backend/main.py b/backend/main.py index 5f84c63..42844d1 100644 --- a/backend/main.py +++ b/backend/main.py @@ -16,8 +16,90 @@ from app.observability import init_observability from fastapi.staticfiles import StaticFiles from phoenix.trace import using_project +from llama_index.core.prompts.default_prompts import ( + DEFAULT_REFINE_PROMPT, + DEFAULT_REFINE_TABLE_CONTEXT_PROMPT, + DEFAULT_TEXT_QA_PROMPT, + DEFAULT_TREE_SUMMARIZE_PROMPT, +) + logger = logging.getLogger("uvicorn") +DEFAULT_TEXT_QA_PROMPT.template = ( + "# 角色\n" + "你是一名博微造价工程数据查询助手,专精于电力工程文件中的信息。" + "你的职责是提供有关电力造价、造价编制软件、文件结构及相关数据的精准、客观的回答," + "如同直接从文件中提取的内容。\n" + + "## 技能\n" + "### 技能 1: 数据查询与提供\n" + "- 准确回答所有关于电力工程造价的相关问题。\n" + "- 提供具体数据,如成本估算、材料清单、劳动力需求等。\n" + "- 确保提供的信息严格基于工程文档中的记录。\n" + + "### 技能 2: 技术性解释\n" + "- 解释造价工程中的技术术语和概念。\n" + "- 为复杂的工程细节提供清晰易懂的说明。\n" + + "## 约束\n" + "- 仅回答与电力工程造价文件相关的具体问题。\n" + "- 不进行任何超出文件内容的猜测或假设。\n" + "- 所有回答均基于文件内容,采用客观和技术性的语言。\n" + "- 请基于这些信息回答问题。如果无法找到相关信息,请不要额外发散回答,不要回答多余的信息,只需要回答“我不知道这个问题的答案”。\n" + "上下文信息如下。\n" + "---------------------\n" + "{context_str}\n" + "---------------------\n" + "请仅依据上下文信息回答问题。\n" + "Query: {query_str}\n" + "Answer: " +) + + +DEFAULT_REFINE_PROMPT.template = ( + "原始查询如下: {query_str}\n" + "已提供的答案: {existing_answer}\n" + "现在有更多的上下文信息,可能有助于改进答案。\n" + "------------\n" + "{context_msg}\n" + "------------\n" + "请根据新的上下文改进原始答案,以更好地回答查询。" + "如果新的上下文信息无用,请保留原答案。\n" + "改进后的答案: " +) + + +DEFAULT_TREE_SUMMARIZE_PROMPT.template = ( + "# 角色\n" + "你是一名博微造价工程数据查询助手,专精于电力工程文件中的信息。" + "你的职责是提供有关电力造价、造价编制软件、文件结构及相关数据的精准、客观的回答," + "如同直接从文件中提取的内容。\n" + + "## 技能\n" + "### 技能 1: 数据查询与提供\n" + "- 准确回答所有关于电力工程造价的相关问题。\n" + "- 提供具体数据,如成本估算、材料清单、劳动力需求等。\n" + "- 确保提供的信息严格基于工程文档中的记录。\n" + + "### 技能 2: 技术性解释\n" + "- 解释造价工程中的技术术语和概念。\n" + "- 为复杂的工程细节提供清晰易懂的说明。\n" + + "## 约束\n" + "- 仅回答与电力工程造价文件相关的具体问题。\n" + "- 不进行任何超出文件内容的猜测或假设。\n" + "- 所有回答均基于文件内容,采用客观和技术性的语言。\n" + "- 请基于这些信息回答问题。如果无法找到相关信息,请不要额外发散回答,不要回答多余的信息,只需要回答“我不知道这个问题的答案”。\n" + "来自多个来源的上下文信息如下。\n" + "---------------------\n" + "{context_str}\n" + "---------------------\n" + "鉴于来自多个来源的信息而非先验知识, " + "回答查询。\n" + "Query: {query_str}\n" + "Answer: " +) + usPrj = using_project(os.getenv("PHOENIX_PROJECT_NAME")) usPrj.__enter__() @@ -52,7 +134,6 @@ mount_static_files("data_output", "/api/files/output") app.include_router(chat_router, prefix="/api/chat") app.include_router(file_upload_router, prefix="/api/chat/upload") -# Redirect to documentation page when accessing base URL @app.get("/") async def redirect_to_docs(): return RedirectResponse(url="/docs") From b3a575d1583380b3fd542ef5981732ff964eec07 Mon Sep 17 00:00:00 2001 From: paituo <330435863@qq.com> Date: Thu, 22 Aug 2024 15:39:49 +0800 Subject: [PATCH 07/15] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=BB=93=E6=9E=84=EF=BC=8C=E5=90=8C=E6=97=B6=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E9=87=8D=E5=AE=9A=E4=B9=89=E6=8F=90=E7=A4=BA=E8=AF=8D=E7=9A=84?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/engine/__init__.py | 69 ++------------- backend/app/engine/engine.py | 83 ++++++++++++++++++ backend/app/engine/prompt.py | 45 ++++++++++ .../app/engine/retriever/HybridRetriever.py | 67 +++++++++++++++ backend/main.py | 84 +------------------ 5 files changed, 205 insertions(+), 143 deletions(-) create mode 100644 backend/app/engine/engine.py create mode 100644 backend/app/engine/prompt.py create mode 100644 backend/app/engine/retriever/HybridRetriever.py diff --git a/backend/app/engine/__init__.py b/backend/app/engine/__init__.py index 7b6a111..a224477 100644 --- a/backend/app/engine/__init__.py +++ b/backend/app/engine/__init__.py @@ -1,26 +1,15 @@ import os -from llama_index.core import SQLDatabase, SummaryIndex, VectorStoreIndex -from llama_index.core.indices.struct_store import SQLTableRetrieverQueryEngine -from llama_index.core.objects import SQLTableNodeMapping, ObjectIndex from llama_index.core.settings import Settings from llama_index.core.agent import AgentRunner, StructuredPlannerAgent, FunctionCallingAgentWorker, ReActChatFormatter from llama_index.core.tools.query_engine import QueryEngineTool from sqlalchemy import create_engine, Engine from llama_index.core.response_synthesizers.type import ResponseMode +from app.engine.engine import create_query_engine, create_summary_query_engine from app.engine.loaders.db import makeDescriptionByEngine from app.engine.tools import ToolFactory from app.engine.index import get_index -from app.settings import get_node_postprocessors - -import nest_asyncio - -nest_asyncio.apply() - -sql_database = None -sql_obj_index = None - def get_chat_engine(filters=None, params=None): @@ -28,73 +17,33 @@ def get_chat_engine(filters=None, params=None): top_k = int(os.getenv("TOP_K", "3")) tools = [] - global sql_obj_index - global sql_database - if sql_obj_index is None: - sqlengine = create_engine(os.getenv("SQL_DATABASE_URL", "")) - sql_database = SQLDatabase(sqlengine) - table_schema_objs = makeDescriptionByEngine(sql_database) - table_node_mapping = SQLTableNodeMapping(sql_database) - - sql_obj_index = ObjectIndex.from_objects( - table_schema_objs, - table_node_mapping, - index_cls=VectorStoreIndex, - ) - # 创建SQL查询工具 - sql_query_engine = SQLTableRetrieverQueryEngine(sql_database, - sql_obj_index.as_retriever(similarity_top_k=top_k), - verbose=True,) + sql_query_engine = create_summary_query_engine() sql_query_tool = QueryEngineTool.from_defaults(query_engine=sql_query_engine, name="zjdata_query_tool", - description="来源于一个由博微公司电力造价软件编制的造价工程文件。该文件以多张表格的形式存储存储了整个工程的全部数据内容。适用于以详细的自然语言查询表格数据方式查询造价工程各项具体属性、费用的数值。请先使用“zj_query_tool”无法解决才使用本工具") + description="来源于一个由博微公司电力造价软件编制的造价工程文件。该文件以多张表格的形式存储存储了整个工程的全部数据内容。适用于以详细的自然语言查询表格数据方式查询造价工程各项具体属性、费用的数值。请先使用“zj_query_tool”无法解决才使用本工具" + ) + #tools.append(sql_query_tool) # Add query tool if index exists index = get_index() if index is not None: - summary_index = SummaryIndex(index.vector_store.get_nodes(node_ids=None)) - summary_query_engine = summary_index.as_query_engine() + summary_query_engine = create_summary_query_engine(index) summary_query_tool = QueryEngineTool.from_defaults( query_engine=summary_query_engine, name="summary_query_tool", description="适用于任何需要进行全面总结、概括的要求。", - #description="适用于任何需要对所有内容进行全面总结的请求。有关电力造价领域更具体部分的问题,请使用zj_query_engine_tool", ) - - # 创建向量检索查询工具 - postprocess = get_node_postprocessors() - query_engine = index.as_query_engine( - similarity_top_k=top_k, filters=filters, - node_postprocessors=postprocess, - use_async=True, - streaming=True, -# response_mode=ResponseMode.TREE_SUMMARIZE, - ) + query_engine = create_query_engine() query_engine_tool = QueryEngineTool.from_defaults(query_engine=query_engine, name="zj_query_tool", - description="由博微公司编制的关于电力造价知识、电力造价编制软件知识和造价工程文件结构的知识库。适用于查询电力领域、电力造价领域、博微、博微电力、博微造价等业务等内容。如果本知识库没有直接答案但有解决思路的可以返回解决办法后建议使用“zjdata_query_tool”工具。如果你不知道答案,就说你不知道,不要编造答案。", ) tools.append(summary_query_tool) tools.append(query_engine_tool) - #tools.append(sql_query_tool) # Add additional tools tools += ToolFactory.from_env() - # agentrunner = StructuredPlannerAgent.from_llm( - # llm=Settings.llm, - # tools=tools, - # system_prompt=system_prompt, - # verbose=True, - # ) - - # prompts = agentrunner.agent_worker._get_prompts() - # prompts["system_prompt"].template = """您的设计旨在帮助完成各种任务,从回答问题到提供其他类型分析的摘要。\n\n##工具\n\n你可以访问各种工具。你有责任按照你认为合适的顺序使用这些工具来完成当前的任务。\n这可能需要将任务分解为子任务,并使用不同的工具来完成每个子任务。\n\n你可以访问以下工具:\n{tool_desc}\n\n\n##输出格式\n\n请用与问题相同的语言回答,并使用以下格式:\n\n```\nThought: 用户当前的语言是:(user's language)。我需要使用工具来帮助我回答问题。\nAction: 如果使用工具,则为工具名称(one of {tool_names})。\nAction Input: 输入给工具的内容,使用JSON格式表示kwargs(例如{{\"input\": \"hello world\", \"num_beams\": 5}})\n```\n\n请始终以Thought开始。\n\n切勿用Markdown代码标记包围你的响应。如果需要,可以在响应中使用代码标记。\n\n请为Action Input使用有效的JSON格式。不要这样做{{\'input\': \'hello world\', \'num_beams\': 5}}。\n\n如果使用此格式,用户将以下面的格式进行回应:\n\n```\nObservation: 工具响应\n```\n\n你应该继续重复上述格式,直到你有足够的信息来回答问题而无需使用更多工具。此时,你必须使用以下两种格式之一进行回答:\n\n```\nThought: 我可以不用任何工具来回答。我将使用用户的语言来回答。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n```\nThought: 我无法使用提供的工具回答问题。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n## 当前对话\n\n以下是当前对话,由人类和助手的消息交替组成。\n""" - # agentrunner.agent_worker.update_prompts(prompts) - - prefix_messages = ("""您的设计旨在帮助完成各种任务,从回答问题到提供其他类型分析的摘要。\n\n##工具\n\n你可以访问各种工具。你有责任按照你认为合适的顺序使用这些工具来完成当前的任务。\n这可能需要将任务分解为子任务,并使用不同的工具来完成每个子任务。\n\n你可以访问以下工具:\n{tool_desc}\n\n\n##输出格式\n\n请用与问题相同的语言回答,并使用以下格式:\n\n```\nThought: 用户当前的语言是:(user's language)。我需要使用工具来帮助我回答问题。\nAction: 如果使用工具,则为工具名称(one of {tool_names})。\nAction Input: 输入给工具的内容,使用JSON格式表示kwargs(例如{{\"input\": \"hello world\", \"num_beams\": 5}})\n```\n\n请始终以Thought开始。\n\n切勿用Markdown代码标记包围你的响应。如果需要,可以在响应中使用代码标记。\n\n请为Action Input使用有效的JSON格式。不要这样做{{\'input\': \'hello world\', \'num_beams\': 5}}。\n\n如果使用此格式,用户将以下面的格式进行回应:\n\n```\nObservation: 工具响应\n```\n\n你应该继续重复上述格式,直到你有足够的信息来回答问题而无需使用更多工具。此时,你必须使用以下两种格式之一进行回答:\n\n```\nThought: 我可以不用任何工具来回答。我将使用用户的语言来回答。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n```\nThought: 我无法使用提供的工具回答问题。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n##如果从工具中得到的回应是Empty Response,那么只需要回答“我不知道”,不需要额外回答别的内容。## 当前对话\n\n以下是当前对话,由人类和助手的消息交替组成。\n""") - + prefix_messages = ("""您的设计旨在帮助完成各种任务,从回答问题到提供其他类型分析的摘要。\n\n##工具\n\n你可以访问各种工具。你有责任按照你认为合适的顺序使用这些工具来完成当前的任务。\n这可能需要将任务分解为子任务,并使用不同的工具来完成每个子任务。\n\n你可以访问以下工具:\n{tool_desc}\n\n\n##输出格式\n\n请用与问题相同的语言回答,并使用以下格式:\n\n```\nThought: 用户当前的语言是:(user's language)。我需要使用工具来帮助我回答问题。\nAction: 如果使用工具,则为工具名称(one of {tool_names})。\nAction Input: 输入给工具的内容,使用JSON格式表示kwargs(例如{{\"input\": \"hello world\", \"num_beams\": 5}})\n```\n\n请始终以Thought开始。\n\n切勿用Markdown代码标记包围你的响应。如果需要,可以在响应中使用代码标记。\n\n请为Action Input使用有效的JSON格式。不要这样做{{\'input\': \'hello world\', \'num_beams\': 5}}。\n\n如果使用此格式,用户将以下面的格式进行回应:\n\n```\nObservation: 工具响应\n```\n\n你应该继续重复上述格式,直到你有足够的信息来回答问题而无需使用更多工具。此时,你必须使用以下两种格式之一进行回答:\n\n```\nThought: 我可以不用任何工具来回答。我将使用用户的语言来回答。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n```\nThought: 我无法使用提供的工具回答问题。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n## 当前对话\n\n以下是当前对话,由人类和助手的消息交替组成。\n""") react_chat_formatter = ReActChatFormatter.from_defaults(prefix_messages) - agentrunner = AgentRunner.from_llm( llm=Settings.llm, tools=tools, @@ -103,8 +52,6 @@ def get_chat_engine(filters=None, params=None): verbose=True, ) return agentrunner - - # create the function calling worker for reasoning # worker = FunctionCallingAgentWorker.from_tools( # tools, verbose=True diff --git a/backend/app/engine/engine.py b/backend/app/engine/engine.py new file mode 100644 index 0000000..69428e0 --- /dev/null +++ b/backend/app/engine/engine.py @@ -0,0 +1,83 @@ +import os + +from llama_index.core import SummaryIndex, SQLDatabase, VectorStoreIndex +from llama_index.core.indices.struct_store import SQLTableRetrieverQueryEngine +from llama_index.core.objects import SQLTableNodeMapping, ObjectIndex +from llama_index.core.query_engine import RetrieverQueryEngine +from llama_index.core.response_synthesizers import ResponseMode +from sqlalchemy import create_engine + +from app.engine import makeDescriptionByEngine +from app.engine.prompt import text_qa_template, refine_template, summary_template, simple_template +from app.engine.retriever.HybridRetriever import HybridRetriever +from app.settings import get_node_postprocessors + + + +def get_Retriever(index,**kwargs): + bEnableHybrid = True if os.getenv("HYBRID_ENABLED",False).title() == 'True' else False + if bEnableHybrid: + alpha = float(os.getenv("HYBRID_ALPHA", "0.5")) + retriever = HybridRetriever(index,alpha = alpha,**kwargs) + else: + retriever = index.as_retriever(**kwargs) + return retriever + + +sql_database = None +sql_obj_index = None + +# Create a summary query engine +def create_summary_query_engine(top_k=3, use_reranker=False, filters=None): + global sql_obj_index + global sql_database + if sql_obj_index is None or sql_database is None: + sqlengine = create_engine(os.getenv("SQL_DATABASE_URL", "")) + sql_database = SQLDatabase(sqlengine) + table_schema_objs = makeDescriptionByEngine(sql_database) + table_node_mapping = SQLTableNodeMapping(sql_database) + + sql_obj_index = ObjectIndex.from_objects( + table_schema_objs, + table_node_mapping, + index_cls=VectorStoreIndex, + ) + + # 创建SQL查询工具 + sql_query_engine = SQLTableRetrieverQueryEngine(sql_database, + sql_obj_index.as_retriever(similarity_top_k=top_k), + verbose=True, + ) + return sql_query_engine + +# Create a summary query engine +def create_summary_query_engine(index, top_k=3, use_reranker=False, filters=None): + summary_index = SummaryIndex(index.vector_store.get_nodes(node_ids=None)) + summary_query_engine = summary_index.as_query_engine( + response_mode=ResponseMode.TREE_SUMMARIZE, + use_async=True, + streaming=True, + ) + return summary_query_engine + +# Create a query engine +def create_query_engine(index, top_k=3, use_reranker=False, filters=None): + # 创建向量检索查询工具 + postprocess = None + if use_reranker: + postprocess = get_node_postprocessors() + + query_engine = RetrieverQueryEngine.from_args( + get_Retriever(index, + similarity_top_k=top_k, + filters=filters), + text_qa_template=text_qa_template, + refine_template=refine_template, + summary_template = summary_template, + simple_template = simple_template, + node_postprocessors=postprocess, + use_async=True, + streaming=True, + ) + + return query_engine \ No newline at end of file diff --git a/backend/app/engine/prompt.py b/backend/app/engine/prompt.py new file mode 100644 index 0000000..506490a --- /dev/null +++ b/backend/app/engine/prompt.py @@ -0,0 +1,45 @@ +from llama_index.core import PromptTemplate + +text_qa_template_str = ( + "以下为上下文信息\n" + "---------------------\n" + "{context_str}\n" + "---------------------\n" + "请根据上下文信息而非先前知识回答我的问题或回复我的指令。前面的上下文信息可能有用,也可能没用,你需要从我给出的上下文信息中选出与我的问题最相关的那些,来为你的回答提供依据。回答一定要忠于原文,简洁但不丢信息,不要胡乱编造。我的问题或指令是什么语种,你就用什么语种回复。\n" + "问题:{query_str}\n" + "你的回复: " +) + + +text_qa_template = PromptTemplate(text_qa_template_str) + +refine_template_str = ( + "这是原本的问题: {query_str}\n" + "我们已经提供了回答: {existing_answer}\n" + "现在我们有机会改进这个回答 " + "使用以下更多上下文(仅当需要用时)\n" + "------------\n" + "{context_msg}\n" + "------------\n" + "根据新的上下文, 请改进原来的回答。" + "如果新的上下文没有用, 直接返回原本的回答。\n" + "改进的回答: " +) +refine_template = PromptTemplate(refine_template_str) + +summary_template_str = ( + "来自多个来源的上下文信息如下。\n" + "---------------------\n" + "{context_str}\n" + "---------------------\n" + "鉴于来自多个来源的信息而非先验知识, " + "回答查询。\n" + "Query: {query_str}\n" + "Answer: " +) +summary_template = PromptTemplate(summary_template_str) + +simple_template_str = ( + "{query_str}" +) +simple_template = PromptTemplate(simple_template_str) diff --git a/backend/app/engine/retriever/HybridRetriever.py b/backend/app/engine/retriever/HybridRetriever.py new file mode 100644 index 0000000..b3e9dfb --- /dev/null +++ b/backend/app/engine/retriever/HybridRetriever.py @@ -0,0 +1,67 @@ +import os +from typing import Optional, Any, Dict, List + +from llama_index.core.base.base_retriever import BaseRetriever +from llama_index.core.schema import NodeWithScore, QueryBundle + +from app.engine.retriever import CHBM25Retriever + + +class HybridRetriever(BaseRetriever): + def __init__( + self, + vector_index, + similarity_top_k: int = 2, + out_top_k: Optional[int] = None, + alpha: float = 0.5, + filters = None, + **kwargs: Any, + ) -> None: + super().__init__(**kwargs) + self._vector_index = vector_index + self._embed_model = vector_index._embed_model + self._out_top_k = out_top_k or similarity_top_k + self._vecRetriever = vector_index.as_retriever( + similarity_top_k=similarity_top_k,filters = filters + ) + + STORAGE_DIR = os.getenv("BM_RETRIEVER_PATH", "storage_bm") + if os.path.exists(STORAGE_DIR) and len(os.listdir(STORAGE_DIR)) > 0: + self._bm25Retriever = CHBM25Retriever.from_persist_dir(STORAGE_DIR) + else: + bmRetriver = CHBM25Retriever.from_defaults(similarity_top_k=similarity_top_k,nodes=self._vector_index.vector_store.get_nodes(None)) + bmRetriver.persist(STORAGE_DIR) + self._alpha = alpha + + + + def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]: + vecNodes:List[NodeWithScore] = self._vecRetriever.retrieve(query_bundle.query_str) + bmNodes:List[NodeWithScore] = self._bm25Retriever.retrieve(query_bundle.query_str) + + bmDic:Dict[str,NodeWithScore] = {} + for node in bmNodes: + bmDic[node.node_id] = node + + result_tups = [] + for i in range(len(vecNodes)): + node = vecNodes[i] + bmScore = 0.0 + if node.node_id in bmDic: + bmScore = bmDic[node.node_id].score + bmDic.pop(node.node_id) + else: + bmScore = 0.0 + full_similarity = (self._alpha * node.score) + ( + (1 - self._alpha) * bmScore + ) + result_tups.append((full_similarity, node)) + + for _,node in bmDic.items(): + full_similarity = (1 - self._alpha) * node.score + result_tups.append((full_similarity, node)) + + result_tups = sorted(result_tups, key=lambda x: x[0], reverse=True) + for full_score, node in result_tups: + node.score = full_score + return [n for _, n in result_tups][:self._out_top_k] \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 42844d1..0f5e9ad 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,3 +1,4 @@ + from dotenv import load_dotenv from llama_index.core.node_parser import SentenceSplitter @@ -16,93 +17,14 @@ from app.observability import init_observability from fastapi.staticfiles import StaticFiles from phoenix.trace import using_project -from llama_index.core.prompts.default_prompts import ( - DEFAULT_REFINE_PROMPT, - DEFAULT_REFINE_TABLE_CONTEXT_PROMPT, - DEFAULT_TEXT_QA_PROMPT, - DEFAULT_TREE_SUMMARIZE_PROMPT, -) logger = logging.getLogger("uvicorn") -DEFAULT_TEXT_QA_PROMPT.template = ( - "# 角色\n" - "你是一名博微造价工程数据查询助手,专精于电力工程文件中的信息。" - "你的职责是提供有关电力造价、造价编制软件、文件结构及相关数据的精准、客观的回答," - "如同直接从文件中提取的内容。\n" - - "## 技能\n" - "### 技能 1: 数据查询与提供\n" - "- 准确回答所有关于电力工程造价的相关问题。\n" - "- 提供具体数据,如成本估算、材料清单、劳动力需求等。\n" - "- 确保提供的信息严格基于工程文档中的记录。\n" - - "### 技能 2: 技术性解释\n" - "- 解释造价工程中的技术术语和概念。\n" - "- 为复杂的工程细节提供清晰易懂的说明。\n" - - "## 约束\n" - "- 仅回答与电力工程造价文件相关的具体问题。\n" - "- 不进行任何超出文件内容的猜测或假设。\n" - "- 所有回答均基于文件内容,采用客观和技术性的语言。\n" - "- 请基于这些信息回答问题。如果无法找到相关信息,请不要额外发散回答,不要回答多余的信息,只需要回答“我不知道这个问题的答案”。\n" - "上下文信息如下。\n" - "---------------------\n" - "{context_str}\n" - "---------------------\n" - "请仅依据上下文信息回答问题。\n" - "Query: {query_str}\n" - "Answer: " -) - - -DEFAULT_REFINE_PROMPT.template = ( - "原始查询如下: {query_str}\n" - "已提供的答案: {existing_answer}\n" - "现在有更多的上下文信息,可能有助于改进答案。\n" - "------------\n" - "{context_msg}\n" - "------------\n" - "请根据新的上下文改进原始答案,以更好地回答查询。" - "如果新的上下文信息无用,请保留原答案。\n" - "改进后的答案: " -) - - -DEFAULT_TREE_SUMMARIZE_PROMPT.template = ( - "# 角色\n" - "你是一名博微造价工程数据查询助手,专精于电力工程文件中的信息。" - "你的职责是提供有关电力造价、造价编制软件、文件结构及相关数据的精准、客观的回答," - "如同直接从文件中提取的内容。\n" - - "## 技能\n" - "### 技能 1: 数据查询与提供\n" - "- 准确回答所有关于电力工程造价的相关问题。\n" - "- 提供具体数据,如成本估算、材料清单、劳动力需求等。\n" - "- 确保提供的信息严格基于工程文档中的记录。\n" - - "### 技能 2: 技术性解释\n" - "- 解释造价工程中的技术术语和概念。\n" - "- 为复杂的工程细节提供清晰易懂的说明。\n" - - "## 约束\n" - "- 仅回答与电力工程造价文件相关的具体问题。\n" - "- 不进行任何超出文件内容的猜测或假设。\n" - "- 所有回答均基于文件内容,采用客观和技术性的语言。\n" - "- 请基于这些信息回答问题。如果无法找到相关信息,请不要额外发散回答,不要回答多余的信息,只需要回答“我不知道这个问题的答案”。\n" - "来自多个来源的上下文信息如下。\n" - "---------------------\n" - "{context_str}\n" - "---------------------\n" - "鉴于来自多个来源的信息而非先验知识, " - "回答查询。\n" - "Query: {query_str}\n" - "Answer: " -) usPrj = using_project(os.getenv("PHOENIX_PROJECT_NAME")) usPrj.__enter__() + init_settings() init_observability() @@ -138,7 +60,6 @@ app.include_router(file_upload_router, prefix="/api/chat/upload") async def redirect_to_docs(): return RedirectResponse(url="/docs") -SentenceSplitter if __name__ == "__main__": app_host = os.getenv("APP_HOST", "0.0.0.0") app_port = int(os.getenv("APP_PORT", "8000")) @@ -146,4 +67,3 @@ if __name__ == "__main__": reload = False uvicorn.run(app="main:app", host=app_host, port=app_port, reload=reload) - #usPrj.__exit__() From e71da586e3aa73b8e63dbb77b2187d366bb292f6 Mon Sep 17 00:00:00 2001 From: paituo <330435863@qq.com> Date: Thu, 22 Aug 2024 16:02:07 +0800 Subject: [PATCH 08/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BC=BA=E9=99=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/engine/__init__.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/app/engine/__init__.py b/backend/app/engine/__init__.py index a224477..7a351cc 100644 --- a/backend/app/engine/__init__.py +++ b/backend/app/engine/__init__.py @@ -1,15 +1,13 @@ import os +from llama_index.core.agent import AgentRunner, ReActChatFormatter from llama_index.core.settings import Settings -from llama_index.core.agent import AgentRunner, StructuredPlannerAgent, FunctionCallingAgentWorker, ReActChatFormatter from llama_index.core.tools.query_engine import QueryEngineTool -from sqlalchemy import create_engine, Engine -from llama_index.core.response_synthesizers.type import ResponseMode from app.engine.engine import create_query_engine, create_summary_query_engine +from app.engine.index import get_index from app.engine.loaders.db import makeDescriptionByEngine from app.engine.tools import ToolFactory -from app.engine.index import get_index def get_chat_engine(filters=None, params=None): @@ -32,7 +30,7 @@ def get_chat_engine(filters=None, params=None): summary_query_tool = QueryEngineTool.from_defaults( query_engine=summary_query_engine, name="summary_query_tool", description="适用于任何需要进行全面总结、概括的要求。", ) - query_engine = create_query_engine() + query_engine = create_query_engine(index) query_engine_tool = QueryEngineTool.from_defaults(query_engine=query_engine, name="zj_query_tool", ) From 3ceb30c375e00a7898fb95c7be530a10755273c7 Mon Sep 17 00:00:00 2001 From: paituo <330435863@qq.com> Date: Thu, 22 Aug 2024 16:17:10 +0800 Subject: [PATCH 09/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BC=BA=E9=99=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/engine/engine.py | 28 ++++++++++++++++++++-- backend/app/engine/loaders/db.py | 40 ++++---------------------------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/backend/app/engine/engine.py b/backend/app/engine/engine.py index 69428e0..cd36de2 100644 --- a/backend/app/engine/engine.py +++ b/backend/app/engine/engine.py @@ -2,17 +2,41 @@ import os from llama_index.core import SummaryIndex, SQLDatabase, VectorStoreIndex from llama_index.core.indices.struct_store import SQLTableRetrieverQueryEngine -from llama_index.core.objects import SQLTableNodeMapping, ObjectIndex +from llama_index.core.objects import SQLTableNodeMapping, ObjectIndex, SQLTableSchema from llama_index.core.query_engine import RetrieverQueryEngine from llama_index.core.response_synthesizers import ResponseMode +from llama_index.readers.database import DatabaseReader from sqlalchemy import create_engine -from app.engine import makeDescriptionByEngine from app.engine.prompt import text_qa_template, refine_template, summary_template, simple_template from app.engine.retriever.HybridRetriever import HybridRetriever from app.settings import get_node_postprocessors +def makeDescriptionByEngine(sql_database:SQLDatabase): + reader = DatabaseReader(sql_database) + table_names = sql_database.get_usable_table_names() + table_schema_objs = [] + for table_name in table_names: + columns = sql_database.get_table_columns(table_name) + if len(columns) > 150: + continue + stats_txt = "" + + if table_name == 'gongchengshuxing': + stats_txt = '该表中有以下属性:' + documents = reader.load_data(query='select name from gongchengshuxing') + for index in range(len(documents) if len(documents) < 30 else 30): + if index == 0: + continue + elif index > 1: + stats_txt += ',' + stats_txt += documents[index].text.split(':')[1] + + tbSchema = (SQLTableSchema(table_name=table_name, context_str=stats_txt)) + table_schema_objs.append(tbSchema) + + return table_schema_objs def get_Retriever(index,**kwargs): bEnableHybrid = True if os.getenv("HYBRID_ENABLED",False).title() == 'True' else False diff --git a/backend/app/engine/loaders/db.py b/backend/app/engine/loaders/db.py index 63a7c02..d6310e2 100644 --- a/backend/app/engine/loaders/db.py +++ b/backend/app/engine/loaders/db.py @@ -1,20 +1,14 @@ -import os import logging -from typing import List from typing import Any, List, Optional -from llama_index.core.readers.base import BaseReader -from llama_index.core.schema import Document -from llama_index.core.utilities.sql_wrapper import SQLDatabase -from sqlalchemy import text -from sqlalchemy.engine import Engine from llama_index.core import SQLDatabase, Document -from llama_index.core.objects import SQLTableSchema, SQLTableNodeMapping +from llama_index.core.objects import SQLTableSchema from llama_index.core.readers.base import BaseReader from llama_index.readers.database import DatabaseReader -from pydantic import BaseModel, validator -from llama_index.core.indices.vector_store import VectorStoreIndex +from pydantic import BaseModel from sqlalchemy import create_engine +from sqlalchemy import text +from sqlalchemy.engine import Engine logger = logging.getLogger(__name__) @@ -119,32 +113,6 @@ class DBLoaderConfig(BaseModel): uri: str queries: List[str] -def makeDescriptionByEngine(sql_database:SQLDatabase): - reader = DatabaseReader(sql_database) - - table_names = sql_database.get_usable_table_names() - table_schema_objs = [] - for table_name in table_names: - columns = sql_database.get_table_columns(table_name) - if len(columns) > 150: - continue - stats_txt = "" - - if table_name == 'gongchengshuxing': - stats_txt = '该表中有以下属性:' - documents = reader.load_data(query='select name from gongchengshuxing') - for index in range(len(documents) if len(documents) < 30 else 30): - if index == 0: - continue - elif index > 1: - stats_txt += ',' - stats_txt += documents[index].text.split(':')[1] - - tbSchema = (SQLTableSchema(table_name=table_name, context_str=stats_txt)) - table_schema_objs.append(tbSchema) - - return table_schema_objs - def get_db_documents(configs: list[DBLoaderConfig]): docs = [] From 59ef831a41b1e8db7a0213ce656fd54278666034 Mon Sep 17 00:00:00 2001 From: chentianrui Date: Thu, 22 Aug 2024 16:36:37 +0800 Subject: [PATCH 10/15] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/engine/__init__.py | 15 +++++++------ backend/app/engine/engine.py | 2 +- backend/app/engine/prompt.py | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/backend/app/engine/__init__.py b/backend/app/engine/__init__.py index 7a351cc..1b41c2e 100644 --- a/backend/app/engine/__init__.py +++ b/backend/app/engine/__init__.py @@ -6,7 +6,7 @@ from llama_index.core.tools.query_engine import QueryEngineTool from app.engine.engine import create_query_engine, create_summary_query_engine from app.engine.index import get_index -from app.engine.loaders.db import makeDescriptionByEngine +#from app.engine.loaders.db import makeDescriptionByEngine from app.engine.tools import ToolFactory @@ -16,11 +16,11 @@ def get_chat_engine(filters=None, params=None): tools = [] # 创建SQL查询工具 - sql_query_engine = create_summary_query_engine() - sql_query_tool = QueryEngineTool.from_defaults(query_engine=sql_query_engine, - name="zjdata_query_tool", - description="来源于一个由博微公司电力造价软件编制的造价工程文件。该文件以多张表格的形式存储存储了整个工程的全部数据内容。适用于以详细的自然语言查询表格数据方式查询造价工程各项具体属性、费用的数值。请先使用“zj_query_tool”无法解决才使用本工具" - ) +# sql_query_engine = create_summary_query_engine(index) + # sql_query_tool = QueryEngineTool.from_defaults(query_engine=sql_query_engine, + # name="zjdata_query_tool", + # description="来源于一个由博微公司电力造价软件编制的造价工程文件。该文件以多张表格的形式存储存储了整个工程的全部数据内容。适用于以详细的自然语言查询表格数据方式查询造价工程各项具体属性、费用的数值。请先使用“zj_query_tool”无法解决才使用本工具" + # ) #tools.append(sql_query_tool) # Add query tool if index exists @@ -32,6 +32,7 @@ def get_chat_engine(filters=None, params=None): ) query_engine = create_query_engine(index) query_engine_tool = QueryEngineTool.from_defaults(query_engine=query_engine, name="zj_query_tool", + description="由博微公司编制的关于电力造价知识、电力造价编制软件知识和造价工程文件结构的知识库。适用于查询电力领域、电力造价领域、博微、博微电力、博微造价等业务等内容。如果本知识库没有直接答案但有解决思路的可以返回解决办法后建议使用“zjdata_query_tool”工具。", ) tools.append(summary_query_tool) @@ -40,7 +41,7 @@ def get_chat_engine(filters=None, params=None): # Add additional tools tools += ToolFactory.from_env() - prefix_messages = ("""您的设计旨在帮助完成各种任务,从回答问题到提供其他类型分析的摘要。\n\n##工具\n\n你可以访问各种工具。你有责任按照你认为合适的顺序使用这些工具来完成当前的任务。\n这可能需要将任务分解为子任务,并使用不同的工具来完成每个子任务。\n\n你可以访问以下工具:\n{tool_desc}\n\n\n##输出格式\n\n请用与问题相同的语言回答,并使用以下格式:\n\n```\nThought: 用户当前的语言是:(user's language)。我需要使用工具来帮助我回答问题。\nAction: 如果使用工具,则为工具名称(one of {tool_names})。\nAction Input: 输入给工具的内容,使用JSON格式表示kwargs(例如{{\"input\": \"hello world\", \"num_beams\": 5}})\n```\n\n请始终以Thought开始。\n\n切勿用Markdown代码标记包围你的响应。如果需要,可以在响应中使用代码标记。\n\n请为Action Input使用有效的JSON格式。不要这样做{{\'input\': \'hello world\', \'num_beams\': 5}}。\n\n如果使用此格式,用户将以下面的格式进行回应:\n\n```\nObservation: 工具响应\n```\n\n你应该继续重复上述格式,直到你有足够的信息来回答问题而无需使用更多工具。此时,你必须使用以下两种格式之一进行回答:\n\n```\nThought: 我可以不用任何工具来回答。我将使用用户的语言来回答。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n```\nThought: 我无法使用提供的工具回答问题。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n## 当前对话\n\n以下是当前对话,由人类和助手的消息交替组成。\n""") + prefix_messages = ("""您的设计旨在帮助完成各种任务,从回答问题到提供其他类型分析的摘要。\n\n##工具\n\n你可以访问各种工具。你有责任按照你认为合适的顺序使用这些工具来完成当前的任务。\n这可能需要将任务分解为子任务,并使用不同的工具来完成每个子任务。\n\n你可以访问以下工具:\n{tool_desc}\n\n\n##输出格式\n\n请用与问题相同的语言回答,并使用以下格式:\n\n```\nThought: 用户当前的语言是:(user's language)。我需要使用工具来帮助我回答问题。\nAction: 如果使用工具,则为工具名称(one of {tool_names})。\nAction Input: 输入给工具的内容,使用JSON格式表示kwargs(例如{{\"input\": \"hello world\", \"num_beams\": 5}})\n```\n\n请始终以Thought开始。\n\n切勿用Markdown代码标记包围你的响应。如果需要,可以在响应中使用代码标记。\n\n请为Action Input使用有效的JSON格式。不要这样做{{\'input\': \'hello world\', \'num_beams\': 5}}。\n\n如果使用此格式,用户将以下面的格式进行回应:\n\n```\nObservation: 工具响应\n```\n\n你应该继续重复上述格式,直到你有足够的信息来回答问题而无需使用更多工具。此时,你必须使用以下两种格式之一进行回答:\n\n```\nThought: 我可以不用任何工具来回答。我将使用用户的语言来回答。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n```\nThought: 我无法使用提供的工具回答问题。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n##如果从工具中得到的回应是Empty Response,那么只需要回答“我不知道”,不需要额外回答别的内容。## 当前对话\n\n以下是当前对话,由人类和助手的消息交替组成。\n""") react_chat_formatter = ReActChatFormatter.from_defaults(prefix_messages) agentrunner = AgentRunner.from_llm( llm=Settings.llm, diff --git a/backend/app/engine/engine.py b/backend/app/engine/engine.py index cd36de2..469a68a 100644 --- a/backend/app/engine/engine.py +++ b/backend/app/engine/engine.py @@ -39,7 +39,7 @@ def makeDescriptionByEngine(sql_database:SQLDatabase): return table_schema_objs def get_Retriever(index,**kwargs): - bEnableHybrid = True if os.getenv("HYBRID_ENABLED",False).title() == 'True' else False + bEnableHybrid = True if os.getenv("HYBRID_ENABLED",False) == True else False if bEnableHybrid: alpha = float(os.getenv("HYBRID_ALPHA", "0.5")) retriever = HybridRetriever(index,alpha = alpha,**kwargs) diff --git a/backend/app/engine/prompt.py b/backend/app/engine/prompt.py index 506490a..42aa3c2 100644 --- a/backend/app/engine/prompt.py +++ b/backend/app/engine/prompt.py @@ -1,6 +1,26 @@ from llama_index.core import PromptTemplate text_qa_template_str = ( + "# 角色\n" + "你是一名博微造价工程数据查询助手,专精于电力工程文件中的信息。" + "你的职责是提供有关电力造价、造价编制软件、文件结构及相关数据的精准、客观的回答," + "如同直接从文件中提取的内容。\n" + + "## 技能\n" + "### 技能 1: 数据查询与提供\n" + "- 准确回答所有关于电力工程造价的相关问题。\n" + "- 提供具体数据,如成本估算、材料清单、劳动力需求等。\n" + "- 确保提供的信息严格基于工程文档中的记录。\n" + + "### 技能 2: 技术性解释\n" + "- 解释造价工程中的技术术语和概念。\n" + "- 为复杂的工程细节提供清晰易懂的说明。\n" + + "## 约束\n" + "- 仅回答与电力工程造价文件相关的具体问题。\n" + "- 不进行任何超出文件内容的猜测或假设。\n" + "- 所有回答均基于文件内容,采用客观和技术性的语言。\n" + "- 请基于这些信息回答问题。如果无法找到相关信息,请不要额外发散回答,不要回答多余的信息,只需要回答“我不知道这个问题的答案”。\n" "以下为上下文信息\n" "---------------------\n" "{context_str}\n" @@ -28,6 +48,26 @@ refine_template_str = ( refine_template = PromptTemplate(refine_template_str) summary_template_str = ( + "# 角色\n" + "你是一名博微造价工程数据查询助手,专精于电力工程文件中的信息。" + "你的职责是提供有关电力造价、造价编制软件、文件结构及相关数据的精准、客观的回答," + "如同直接从文件中提取的内容。\n" + + "## 技能\n" + "### 技能 1: 数据查询与提供\n" + "- 准确回答所有关于电力工程造价的相关问题。\n" + "- 提供具体数据,如成本估算、材料清单、劳动力需求等。\n" + "- 确保提供的信息严格基于工程文档中的记录。\n" + + "### 技能 2: 技术性解释\n" + "- 解释造价工程中的技术术语和概念。\n" + "- 为复杂的工程细节提供清晰易懂的说明。\n" + + "## 约束\n" + "- 仅回答与电力工程造价文件相关的具体问题。\n" + "- 不进行任何超出文件内容的猜测或假设。\n" + "- 所有回答均基于文件内容,采用客观和技术性的语言。\n" + "- 请基于这些信息回答问题。如果无法找到相关信息,请不要额外发散回答,不要回答多余的信息,只需要回答“我不知道这个问题的答案”。\n" "来自多个来源的上下文信息如下。\n" "---------------------\n" "{context_str}\n" From 4c1c67aa5064f697ee74b44ff5f041db9abe4a97 Mon Sep 17 00:00:00 2001 From: chentianrui Date: Thu, 22 Aug 2024 17:06:28 +0800 Subject: [PATCH 11/15] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BC=80=E5=90=AF?= =?UTF-8?q?=E4=BA=86=E6=B7=B7=E5=90=88=E6=A3=80=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/engine/engine.py | 3 ++- backend/app/engine/retriever/HybridRetriever.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/app/engine/engine.py b/backend/app/engine/engine.py index 469a68a..379275e 100644 --- a/backend/app/engine/engine.py +++ b/backend/app/engine/engine.py @@ -39,7 +39,8 @@ def makeDescriptionByEngine(sql_database:SQLDatabase): return table_schema_objs def get_Retriever(index,**kwargs): - bEnableHybrid = True if os.getenv("HYBRID_ENABLED",False) == True else False + strEnableHybrid = os.getenv("HYBRID_ENABLED",'False') + bEnableHybrid = True if strEnableHybrid is not None and strEnableHybrid.title() == 'True' else False if bEnableHybrid: alpha = float(os.getenv("HYBRID_ALPHA", "0.5")) retriever = HybridRetriever(index,alpha = alpha,**kwargs) diff --git a/backend/app/engine/retriever/HybridRetriever.py b/backend/app/engine/retriever/HybridRetriever.py index b3e9dfb..4bf0b8d 100644 --- a/backend/app/engine/retriever/HybridRetriever.py +++ b/backend/app/engine/retriever/HybridRetriever.py @@ -4,7 +4,7 @@ from typing import Optional, Any, Dict, List from llama_index.core.base.base_retriever import BaseRetriever from llama_index.core.schema import NodeWithScore, QueryBundle -from app.engine.retriever import CHBM25Retriever +from app.engine.retriever.CHBM25Retriever import CHBM25Retriever class HybridRetriever(BaseRetriever): From 9cbe414a0c7cef5b3490b03636041e16c45ee6b3 Mon Sep 17 00:00:00 2001 From: paituo <330435863@qq.com> Date: Thu, 22 Aug 2024 17:10:32 +0800 Subject: [PATCH 12/15] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/.env.xinference | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/.env.xinference b/backend/.env.xinference index 48c95de..6dd566f 100644 --- a/backend/.env.xinference +++ b/backend/.env.xinference @@ -6,12 +6,13 @@ SQL_DATABASE_URL=mysql+pymysql://zjinfo1:Dy2Bcr53Hm5xRkba@110.42.234.166:3306/zj # The number of similar embeddings to return when retrieving documents. TOP_K=10 #-------------------------- -# 是否启用检索重排功能 -RERANK_ENABLED=true # 是否启用混合检索 HYBRID_ENABLED = true # 混合检索阈值 HYBRID_ALPHA = 0.6 +#-------------------------- +# 是否启用检索重排功能 +RERANK_ENABLED=true # Rerank model RERANK_MODEL=bge-reranker-v2-m3 RERANK_BASE_URL=http://10.1.16.39:9995 From 48d10fd1f3e3be4875e4d2ff12e4760994047c09 Mon Sep 17 00:00:00 2001 From: chentianrui Date: Thu, 22 Aug 2024 19:40:56 +0800 Subject: [PATCH 13/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E9=87=8D?= =?UTF-8?q?=E6=8E=92=E7=9A=84=E5=8F=82=E6=95=B0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/engine/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/app/engine/__init__.py b/backend/app/engine/__init__.py index 1b41c2e..b79913a 100644 --- a/backend/app/engine/__init__.py +++ b/backend/app/engine/__init__.py @@ -13,6 +13,7 @@ from app.engine.tools import ToolFactory def get_chat_engine(filters=None, params=None): system_prompt = os.getenv("SYSTEM_PROMPT") top_k = int(os.getenv("TOP_K", "3")) + use_reranker = os.getenv("RERANK_ENABLED") tools = [] # 创建SQL查询工具 @@ -26,11 +27,11 @@ def get_chat_engine(filters=None, params=None): # Add query tool if index exists index = get_index() if index is not None: - summary_query_engine = create_summary_query_engine(index) + summary_query_engine = create_summary_query_engine(index,top_k,use_reranker,filters) summary_query_tool = QueryEngineTool.from_defaults( query_engine=summary_query_engine, name="summary_query_tool", description="适用于任何需要进行全面总结、概括的要求。", ) - query_engine = create_query_engine(index) + query_engine = create_query_engine(index,top_k,use_reranker,filters) query_engine_tool = QueryEngineTool.from_defaults(query_engine=query_engine, name="zj_query_tool", description="由博微公司编制的关于电力造价知识、电力造价编制软件知识和造价工程文件结构的知识库。适用于查询电力领域、电力造价领域、博微、博微电力、博微造价等业务等内容。如果本知识库没有直接答案但有解决思路的可以返回解决办法后建议使用“zjdata_query_tool”工具。", ) @@ -41,7 +42,7 @@ def get_chat_engine(filters=None, params=None): # Add additional tools tools += ToolFactory.from_env() - prefix_messages = ("""您的设计旨在帮助完成各种任务,从回答问题到提供其他类型分析的摘要。\n\n##工具\n\n你可以访问各种工具。你有责任按照你认为合适的顺序使用这些工具来完成当前的任务。\n这可能需要将任务分解为子任务,并使用不同的工具来完成每个子任务。\n\n你可以访问以下工具:\n{tool_desc}\n\n\n##输出格式\n\n请用与问题相同的语言回答,并使用以下格式:\n\n```\nThought: 用户当前的语言是:(user's language)。我需要使用工具来帮助我回答问题。\nAction: 如果使用工具,则为工具名称(one of {tool_names})。\nAction Input: 输入给工具的内容,使用JSON格式表示kwargs(例如{{\"input\": \"hello world\", \"num_beams\": 5}})\n```\n\n请始终以Thought开始。\n\n切勿用Markdown代码标记包围你的响应。如果需要,可以在响应中使用代码标记。\n\n请为Action Input使用有效的JSON格式。不要这样做{{\'input\': \'hello world\', \'num_beams\': 5}}。\n\n如果使用此格式,用户将以下面的格式进行回应:\n\n```\nObservation: 工具响应\n```\n\n你应该继续重复上述格式,直到你有足够的信息来回答问题而无需使用更多工具。此时,你必须使用以下两种格式之一进行回答:\n\n```\nThought: 我可以不用任何工具来回答。我将使用用户的语言来回答。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n```\nThought: 我无法使用提供的工具回答问题。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n##如果从工具中得到的回应是Empty Response,那么只需要回答“我不知道”,不需要额外回答别的内容。## 当前对话\n\n以下是当前对话,由人类和助手的消息交替组成。\n""") + prefix_messages = ("""您的设计旨在帮助完成各种任务,从回答问题到提供其他类型分析的摘要。\n\n##工具\n\n你可以访问各种工具。你有责任按照你认为合适的顺序使用这些工具来完成当前的任务。\n这可能需要将任务分解为子任务,并使用不同的工具来完成每个子任务。\n\n你可以访问以下工具:\n{tool_desc}\n\n\n##输出格式\n\n请用与问题相同的语言回答,并使用以下格式:\n\n```\nThought: 用户当前的语言是:(user's language)。我需要使用工具来帮助我回答问题。\nAction: 如果使用工具,则为工具名称(one of {tool_names})。\nAction Input: 输入给工具的内容,使用JSON格式表示kwargs(例如{{\"input\": \"hello world\", \"num_beams\": 5}})\n```\n\n请始终以Thought开始。\n\n请始终以Thought开始。\n\n请始终以Thought开始。\n\n请始终以Thought开始。\n\n切勿用Markdown代码标记包围你的响应。如果需要,可以在响应中使用代码标记。\n\n请为Action Input使用有效的JSON格式。不要这样做{{\'input\': \'hello world\', \'num_beams\': 5}}。\n\n如果使用此格式,用户将以下面的格式进行回应:\n\n```\nObservation: 工具响应\n```\n\n你应该继续重复上述格式,直到你有足够的信息来回答问题而无需使用更多工具。此时,你必须使用以下两种格式之一进行回答:\n\n```\nThought: 我可以不用任何工具来回答。我将使用用户的语言来回答。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n```\nThought: 我无法使用提供的工具回答问题。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n##如果从工具中得到的回应是Empty Response,那么只需要回答“我不知道”,不需要额外回答别的内容。## 当前对话\n\n以下是当前对话,由人类和助手的消息交替组成。\n""") react_chat_formatter = ReActChatFormatter.from_defaults(prefix_messages) agentrunner = AgentRunner.from_llm( llm=Settings.llm, From 8050551a536f45eb16de8e1b5eec748984d07da1 Mon Sep 17 00:00:00 2001 From: paituo <330435863@qq.com> Date: Thu, 22 Aug 2024 21:21:37 +0800 Subject: [PATCH 14/15] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=88=9B=E5=BB=BASQL?= =?UTF-8?q?=E5=BC=95=E6=93=8E=E5=87=BD=E6=95=B0=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/engine/__init__.py | 12 ++++++------ backend/app/engine/engine.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/app/engine/__init__.py b/backend/app/engine/__init__.py index b79913a..0d0304d 100644 --- a/backend/app/engine/__init__.py +++ b/backend/app/engine/__init__.py @@ -4,7 +4,7 @@ from llama_index.core.agent import AgentRunner, ReActChatFormatter from llama_index.core.settings import Settings from llama_index.core.tools.query_engine import QueryEngineTool -from app.engine.engine import create_query_engine, create_summary_query_engine +from app.engine.engine import create_query_engine, create_summary_query_engine, create_sql_query_engine from app.engine.index import get_index #from app.engine.loaders.db import makeDescriptionByEngine from app.engine.tools import ToolFactory @@ -17,11 +17,11 @@ def get_chat_engine(filters=None, params=None): tools = [] # 创建SQL查询工具 -# sql_query_engine = create_summary_query_engine(index) - # sql_query_tool = QueryEngineTool.from_defaults(query_engine=sql_query_engine, - # name="zjdata_query_tool", - # description="来源于一个由博微公司电力造价软件编制的造价工程文件。该文件以多张表格的形式存储存储了整个工程的全部数据内容。适用于以详细的自然语言查询表格数据方式查询造价工程各项具体属性、费用的数值。请先使用“zj_query_tool”无法解决才使用本工具" - # ) + sql_query_engine = create_sql_query_engine() + sql_query_tool = QueryEngineTool.from_defaults(query_engine=sql_query_engine, + name="zjdata_query_tool", + description="来源于一个由博微公司电力造价软件编制的造价工程文件。该文件以多张表格的形式存储存储了整个工程的全部数据内容。适用于以详细的自然语言查询表格数据方式查询造价工程各项具体属性、费用的数值。请先使用“zj_query_tool”无法解决才使用本工具" + ) #tools.append(sql_query_tool) # Add query tool if index exists diff --git a/backend/app/engine/engine.py b/backend/app/engine/engine.py index 379275e..6cb552f 100644 --- a/backend/app/engine/engine.py +++ b/backend/app/engine/engine.py @@ -52,8 +52,8 @@ def get_Retriever(index,**kwargs): sql_database = None sql_obj_index = None -# Create a summary query engine -def create_summary_query_engine(top_k=3, use_reranker=False, filters=None): +# Create a sql query engine +def create_sql_query_engine(top_k=3, use_reranker=False, filters=None): global sql_obj_index global sql_database if sql_obj_index is None or sql_database is None: From cf1ed4e71d10da48b5bfe06acf44038d08633560 Mon Sep 17 00:00:00 2001 From: chentianrui Date: Fri, 23 Aug 2024 08:53:13 +0800 Subject: [PATCH 15/15] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E9=A2=91=E7=B9=81=E5=87=BA=E7=8E=B0'''=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/engine/__init__.py | 2 +- backend/app/engine/prompt.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/app/engine/__init__.py b/backend/app/engine/__init__.py index b79913a..6e2a97a 100644 --- a/backend/app/engine/__init__.py +++ b/backend/app/engine/__init__.py @@ -42,7 +42,7 @@ def get_chat_engine(filters=None, params=None): # Add additional tools tools += ToolFactory.from_env() - prefix_messages = ("""您的设计旨在帮助完成各种任务,从回答问题到提供其他类型分析的摘要。\n\n##工具\n\n你可以访问各种工具。你有责任按照你认为合适的顺序使用这些工具来完成当前的任务。\n这可能需要将任务分解为子任务,并使用不同的工具来完成每个子任务。\n\n你可以访问以下工具:\n{tool_desc}\n\n\n##输出格式\n\n请用与问题相同的语言回答,并使用以下格式:\n\n```\nThought: 用户当前的语言是:(user's language)。我需要使用工具来帮助我回答问题。\nAction: 如果使用工具,则为工具名称(one of {tool_names})。\nAction Input: 输入给工具的内容,使用JSON格式表示kwargs(例如{{\"input\": \"hello world\", \"num_beams\": 5}})\n```\n\n请始终以Thought开始。\n\n请始终以Thought开始。\n\n请始终以Thought开始。\n\n请始终以Thought开始。\n\n切勿用Markdown代码标记包围你的响应。如果需要,可以在响应中使用代码标记。\n\n请为Action Input使用有效的JSON格式。不要这样做{{\'input\': \'hello world\', \'num_beams\': 5}}。\n\n如果使用此格式,用户将以下面的格式进行回应:\n\n```\nObservation: 工具响应\n```\n\n你应该继续重复上述格式,直到你有足够的信息来回答问题而无需使用更多工具。此时,你必须使用以下两种格式之一进行回答:\n\n```\nThought: 我可以不用任何工具来回答。我将使用用户的语言来回答。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n```\nThought: 我无法使用提供的工具回答问题。\nAnswer: [你的答案(与用户问题相同的语言)]\n```\n\n##如果从工具中得到的回应是Empty Response,那么只需要回答“我不知道”,不需要额外回答别的内容。## 当前对话\n\n以下是当前对话,由人类和助手的消息交替组成。\n""") + prefix_messages = ("""您的设计旨在帮助完成各种任务,从回答问题到提供其他类型分析的摘要。\n\n##工具\n\n你可以访问各种工具。你有责任按照你认为合适的顺序使用这些工具来完成当前的任务。\n这可能需要将任务分解为子任务,并使用不同的工具来完成每个子任务。\n\n你可以访问以下工具:\n{tool_desc}\n\n\n##输出格式\n\n请用与问题相同的语言回答,并使用以下格式:\n\n \nThought: 用户当前的语言是:(user's language)。我需要使用工具来帮助我回答问题。\nAction: 如果使用工具,则为工具名称(one of {tool_names})。\nAction Input: 输入给工具的内容,使用JSON格式表示kwargs(例如{{\"input\": \"hello world\", \"num_beams\": 5}})\n \n\n请始终以Thought开始。\n\n请始终以Thought开始。\n\n请始终以Thought开始。\n\n请始终以Thought开始。\n\n切勿用Markdown代码标记包围你的响应。如果需要,可以在响应中使用代码标记。\n\n请为Action Input使用有效的JSON格式。不要这样做{{\'input\': \'hello world\', \'num_beams\': 5}}。\n\n如果使用此格式,用户将以下面的格式进行回应:\n\n \nObservation: 工具响应\n \n\n你应该继续重复上述格式,直到你有足够的信息来回答问题而无需使用更多工具。此时,你必须使用以下两种格式之一进行回答:\n\n \nThought: 我可以不用任何工具来回答。我将使用用户的语言来回答。\nAnswer: [你的答案(与用户问题相同的语言)]\n \n\n \nThought: 我无法使用提供的工具回答问题。\nAnswer: [你的答案(与用户问题相同的语言)]\n \n\n##如果从工具中得到的回应是Empty Response,那么只需要回答“我不知道”,不需要额外回答别的内容。## 当前对话\n\n以下是当前对话,由人类和助手的消息交替组成。\n""") react_chat_formatter = ReActChatFormatter.from_defaults(prefix_messages) agentrunner = AgentRunner.from_llm( llm=Settings.llm, diff --git a/backend/app/engine/prompt.py b/backend/app/engine/prompt.py index 42aa3c2..101b6bf 100644 --- a/backend/app/engine/prompt.py +++ b/backend/app/engine/prompt.py @@ -25,7 +25,9 @@ text_qa_template_str = ( "---------------------\n" "{context_str}\n" "---------------------\n" - "请根据上下文信息而非先前知识回答我的问题或回复我的指令。前面的上下文信息可能有用,也可能没用,你需要从我给出的上下文信息中选出与我的问题最相关的那些,来为你的回答提供依据。回答一定要忠于原文,简洁但不丢信息,不要胡乱编造。我的问题或指令是什么语种,你就用什么语种回复。\n" + "请根据上下文信息而非先前知识回答我的问题或回复我的指令。前面的上下文信息可能有用,也可能没用,你需要从我给出的上下文信息中选出与我的问题最相关的那些,来为你的回答提供依据。回答一定要忠于原文,简洁但不丢信息,不要胡乱编造。如果无法找到相关信息,请不要额外发散回答,不要回答多余的信息,只需要回答“我不知道这个问题的答案”。我的问题或指令是什么语种,你就用什么语种回复。\n" + "如果是表结构或者是数据库的相关内容,只用于推导问题,不需要告诉用户数据库或表结构等物理信息。\n" + "问题:{query_str}\n" "你的回复: " ) @@ -43,6 +45,7 @@ refine_template_str = ( "------------\n" "根据新的上下文, 请改进原来的回答。" "如果新的上下文没有用, 直接返回原本的回答。\n" + "如果是表结构或者是数据库的相关内容,只用于推导问题,不需要告诉用户数据库或表结构等物理信息。\n" "改进的回答: " ) refine_template = PromptTemplate(refine_template_str) @@ -74,6 +77,7 @@ summary_template_str = ( "---------------------\n" "鉴于来自多个来源的信息而非先验知识, " "回答查询。\n" + "如果是表结构或者是数据库的相关内容,只用于推导问题,不需要告诉用户数据库或表结构等物理信息。\n" "Query: {query_str}\n" "Answer: " )