diff --git a/examples/lightrag_oracle_demo.py b/examples/lightrag_oracle_demo.py index bbb69319..533fe830 100644 --- a/examples/lightrag_oracle_demo.py +++ b/examples/lightrag_oracle_demo.py @@ -105,7 +105,7 @@ async def main(): rag.key_string_value_json_storage_cls.db = oracle_db rag.vector_db_storage_cls.db = oracle_db # add embedding_func for graph database, it's deleted in commit 5661d76860436f7bf5aef2e50d9ee4a59660146c - rag.chunk_entity_relation_graph.embedding_func = rag.embedding_func + rag.chunk_entity_relation_graph._embedding_func = rag.embedding_func # Extract and Insert into LightRAG storage with open("./dickens/demo.txt", "r", encoding="utf-8") as f: diff --git a/lightrag/kg/milvus_impl.py b/lightrag/kg/milvus_impl.py index bf20ffd7..54da22bd 100644 --- a/lightrag/kg/milvus_impl.py +++ b/lightrag/kg/milvus_impl.py @@ -83,7 +83,7 @@ async def query(self, query, top_k=5): output_fields=list(self.meta_fields), search_params={"metric_type": "COSINE", "params": {"radius": 0.2}}, ) - print(results) + print(f"Query VectorDB Results: {len(results[0])}\n * * * * * {results}\n* * * * *") return [ {**dp["entity"], "id": dp["id"], "distance": dp["distance"]} for dp in results[0] diff --git a/lightrag/kg/neo4j_impl.py b/lightrag/kg/neo4j_impl.py index 884fcb40..4a4b3156 100644 --- a/lightrag/kg/neo4j_impl.py +++ b/lightrag/kg/neo4j_impl.py @@ -43,13 +43,26 @@ def __init__(self, namespace, global_config, embedding_func): "NEO4J_DATABASE" ) # If this param is None, the home database will be used. If it is not None, the specified database will be used. self._DATABASE = DATABASE + # 增加默认参数 by bumaple 2025-01-10 + self._timeout = 600 + self._conn_pool_size = 20 + self._driver: AsyncDriver = AsyncGraphDatabase.driver( - URI, auth=(USERNAME, PASSWORD) + URI, auth=(USERNAME, PASSWORD), + connection_acquisition_timeout=self._timeout, + max_connection_lifetime=self._timeout, + max_connection_pool_size=self._conn_pool_size, + connection_timeout=self._timeout, ) _database_name = "home database" if DATABASE is None else f"database {DATABASE}" - with GraphDatabase.driver(URI, auth=(USERNAME, PASSWORD)) as _sync_driver: + with GraphDatabase.driver(URI, auth=(USERNAME, PASSWORD), + connection_acquisition_timeout=self._timeout, + max_connection_lifetime=self._timeout, + max_connection_pool_size=self._conn_pool_size, + connection_timeout=self._timeout, + ) as _sync_driver: try: - with _sync_driver.session(database=DATABASE) as session: + with _sync_driver.session(database=DATABASE, connection_acquisition_timeout=self._timeout) as session: try: session.run("MATCH (n) RETURN n LIMIT 0") logger.info(f"Connected to {DATABASE} at {URI}") @@ -101,7 +114,7 @@ async def index_done_callback(self): async def has_node(self, node_id: str) -> bool: entity_name_label = node_id.strip('"') - async with self._driver.session(database=self._DATABASE) as session: + async with self._driver.session(database=self._DATABASE, connection_acquisition_timeout=self._timeout) as session: query = ( f"MATCH (n:`{entity_name_label}`) RETURN count(n) > 0 AS node_exists" ) @@ -116,7 +129,7 @@ async def has_edge(self, source_node_id: str, target_node_id: str) -> bool: entity_name_label_source = source_node_id.strip('"') entity_name_label_target = target_node_id.strip('"') - async with self._driver.session(database=self._DATABASE) as session: + async with self._driver.session(database=self._DATABASE, connection_acquisition_timeout=self._timeout) as session: query = ( f"MATCH (a:`{entity_name_label_source}`)-[r]-(b:`{entity_name_label_target}`) " "RETURN COUNT(r) > 0 AS edgeExists" @@ -129,7 +142,7 @@ async def has_edge(self, source_node_id: str, target_node_id: str) -> bool: return single_result["edgeExists"] async def get_node(self, node_id: str) -> Union[dict, None]: - async with self._driver.session(database=self._DATABASE) as session: + async with self._driver.session(database=self._DATABASE, connection_acquisition_timeout=self._timeout) as session: entity_name_label = node_id.strip('"') query = f"MATCH (n:`{entity_name_label}`) RETURN n" result = await session.run(query) @@ -146,7 +159,7 @@ async def get_node(self, node_id: str) -> Union[dict, None]: async def node_degree(self, node_id: str) -> int: entity_name_label = node_id.strip('"') - async with self._driver.session(database=self._DATABASE) as session: + async with self._driver.session(database=self._DATABASE, connection_acquisition_timeout=self._timeout) as session: query = f""" MATCH (n:`{entity_name_label}`) RETURN COUNT{{ (n)--() }} AS totalEdgeCount @@ -193,7 +206,7 @@ async def get_edge( Returns: list: List of all relationships/edges found """ - async with self._driver.session(database=self._DATABASE) as session: + async with self._driver.session(database=self._DATABASE, connection_acquisition_timeout=self._timeout) as session: query = f""" MATCH (start:`{entity_name_label_source}`)-[r]->(end:`{entity_name_label_target}`) RETURN properties(r) as edge_properties @@ -224,7 +237,7 @@ async def get_node_edges(self, source_node_id: str) -> List[Tuple[str, str]]: query = f"""MATCH (n:`{node_label}`) OPTIONAL MATCH (n)-[r]-(connected) RETURN n, r, connected""" - async with self._driver.session(database=self._DATABASE) as session: + async with self._driver.session(database=self._DATABASE, connection_acquisition_timeout=self._timeout) as session: results = await session.run(query) edges = [] async for record in results: @@ -279,7 +292,7 @@ async def _do_upsert(tx: AsyncManagedTransaction): ) try: - async with self._driver.session(database=self._DATABASE) as session: + async with self._driver.session(database=self._DATABASE, connection_acquisition_timeout=self._timeout) as session: await session.execute_write(_do_upsert) except Exception as e: logger.error(f"Error during upsert: {str(e)}") @@ -326,7 +339,7 @@ async def _do_upsert_edge(tx: AsyncManagedTransaction): ) try: - async with self._driver.session(database=self._DATABASE) as session: + async with self._driver.session(database=self._DATABASE, connection_acquisition_timeout=self._timeout) as session: await session.execute_write(_do_upsert_edge) except Exception as e: logger.error(f"Error during edge upsert: {str(e)}") diff --git a/lightrag/kg/tidb_impl.py b/lightrag/kg/tidb_impl.py index 2cf698e1..be5ae634 100644 --- a/lightrag/kg/tidb_impl.py +++ b/lightrag/kg/tidb_impl.py @@ -183,7 +183,7 @@ async def upsert(self, data: dict[str, dict]): "tokens": item["tokens"], "chunk_order_index": item["chunk_order_index"], "full_doc_id": item["full_doc_id"], - "content_vector": f"{item["__vector__"].tolist()}", + "content_vector": f"{item['__vector__'].tolist()}", "workspace": self.db.workspace, } ) @@ -286,7 +286,7 @@ async def upsert(self, data: dict[str, dict]): "id": item["id"], "name": item["entity_name"], "content": item["content"], - "content_vector": f"{item["content_vector"].tolist()}", + "content_vector": f"{item['content_vector'].tolist()}", "workspace": self.db.workspace, } # update entity_id if node inserted by graph_storage_instance before @@ -308,7 +308,7 @@ async def upsert(self, data: dict[str, dict]): "source_name": item["src_id"], "target_name": item["tgt_id"], "content": item["content"], - "content_vector": f"{item["content_vector"].tolist()}", + "content_vector": f"{item['content_vector'].tolist()}", "workspace": self.db.workspace, } # update relation_id if node inserted by graph_storage_instance before diff --git a/lightrag/lightrag.py b/lightrag/lightrag.py index cbe49da2..e7ae8281 100644 --- a/lightrag/lightrag.py +++ b/lightrag/lightrag.py @@ -1,5 +1,7 @@ import asyncio import os + +from lightrag.operate import chunking_by_markdown_header from tqdm.asyncio import tqdm as tqdm_async from dataclasses import asdict, dataclass, field from datetime import datetime @@ -12,6 +14,8 @@ ) from .operate import ( chunking_by_token_size, + chunking_by_markdown_header, + chunking_by_markdown_text, extract_entities, # local_query,global_query,hybrid_query, kg_query, @@ -43,7 +47,7 @@ JsonDocStatusStorage, ) -from .prompt import GRAPH_FIELD_SEP +from .prompt_cn import GRAPH_FIELD_SEP # future KG integrations @@ -183,13 +187,21 @@ class LightRAG: addon_params: dict = field(default_factory=dict) convert_response_to_json_func: callable = convert_response_to_json + # 自定义新增 主实体编号、名称 by bumaple 2024-12-03 + extend_entity_title: str = '' + extend_entity_sn: str = '' + # 自定义新增 块类型 by bumaple 2024-12-11 + chunk_type: str = 'token_size' + # 自定义新增 块标题层级 by bumaple 2024-12-11 + chunk_header_level: int = 2 + # Add new field for document status storage type doc_status_storage: str = field(default="JsonDocStatusStorage") def __post_init__(self): - log_file = os.path.join("lightrag.log") - set_logger(log_file) - logger.setLevel(self.log_level) + log_file = os.path.join(self.working_dir, "lightrag.log") + set_logger(log_file, self.log_level) + # logger.setLevel(self.log_level) logger.info(f"Logger initialized for working directory: {self.working_dir}") @@ -372,18 +384,48 @@ async def ainsert(self, string_or_strings): await self.doc_status.upsert({doc_id: doc_status}) # Generate chunks from document - chunks = { - compute_mdhash_id(dp["content"], prefix="chunk-"): { - **dp, - "full_doc_id": doc_id, + if self.chunk_type == "markdown_header": + chunks = { + compute_mdhash_id(dp["content"], prefix="chunk-"): { + **dp, + "full_doc_id": doc_id, + } + for dp in chunking_by_markdown_header( + doc["content"], + overlap_token_size=self.chunk_overlap_token_size, + max_token_size=self.chunk_token_size, + extend_entity_title=self.extend_entity_title, + extend_entity_sn=self.extend_entity_sn, + chunk_header_level=self.chunk_header_level, + ) + } + elif self.chunk_type == "markdown_text": + chunks = { + compute_mdhash_id(dp["content"], prefix="chunk-"): { + **dp, + "full_doc_id": doc_id, + } + for dp in chunking_by_markdown_text( + doc["content"], + overlap_token_size=self.chunk_overlap_token_size, + max_token_size=self.chunk_token_size, + extend_entity_title=self.extend_entity_title, + extend_entity_sn=self.extend_entity_sn, + ) + } + else: + chunks = { + compute_mdhash_id(dp["content"], prefix="chunk-"): { + **dp, + "full_doc_id": doc_id, + } + for dp in chunking_by_token_size( + doc["content"], + overlap_token_size=self.chunk_overlap_token_size, + max_token_size=self.chunk_token_size, + tiktoken_model=self.tiktoken_model_name, + ) } - for dp in chunking_by_token_size( - doc["content"], - overlap_token_size=self.chunk_overlap_token_size, - max_token_size=self.chunk_token_size, - tiktoken_model=self.tiktoken_model_name, - ) - } # Update status with chunks information doc_status.update( diff --git a/lightrag/llm.py b/lightrag/llm.py index 0c17019a..25e3f819 100644 --- a/lightrag/llm.py +++ b/lightrag/llm.py @@ -17,6 +17,7 @@ RateLimitError, APITimeoutError, AsyncAzureOpenAI, + BadRequestError ) from pydantic import BaseModel, Field from tenacity import ( @@ -48,7 +49,7 @@ stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10), retry=retry_if_exception_type( - (RateLimitError, APIConnectionError, APITimeoutError) + (RateLimitError, APIConnectionError, APITimeoutError, BadRequestError) ), ) async def openai_complete_if_cache( @@ -893,6 +894,7 @@ async def openai_embedding( model: str = "text-embedding-3-small", base_url: str = None, api_key: str = None, + timeout: float = 60, ) -> np.ndarray: if api_key: os.environ["OPENAI_API_KEY"] = api_key @@ -901,7 +903,7 @@ async def openai_embedding( AsyncOpenAI() if base_url is None else AsyncOpenAI(base_url=base_url) ) response = await openai_async_client.embeddings.create( - model=model, input=texts, encoding_format="float" + model=model, input=texts, encoding_format="float", timeout=timeout ) return np.array([dp.embedding for dp in response.data]) diff --git a/lightrag/operate.py b/lightrag/operate.py index b2c4d215..612caafc 100644 --- a/lightrag/operate.py +++ b/lightrag/operate.py @@ -29,8 +29,11 @@ TextChunkSchema, QueryParam, ) -from .prompt import GRAPH_FIELD_SEP, PROMPTS import time +from .prompt_cn import GRAPH_FIELD_SEP, PROMPTS +# 引入自定义的文本分割器 bumaple 2024-12-10 +from langchain_text_splitters import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter, MarkdownTextSplitter +from langchain_core.documents import Document def chunking_by_token_size( @@ -54,6 +57,127 @@ def chunking_by_token_size( return results +# 引入Markdown的文本分割器 bumaple 2024-12-10 +def chunking_by_markdown_header( + content: str, overlap_token_size=128, max_token_size=1024, + extend_entity_title: str = '', + extend_entity_sn: str = '', + chunk_header_level: int = 2, +): + extend_entity_content = f"{extend_entity_sn} 《{extend_entity_title}》\n" + results = [] + md_split_contents = _chunking_text_by_markerdown_header_loop( + max_token_size=max_token_size, + header_level=1, + content=content + ) + + # 再用文本分割器再分割,控制不超过制定长度 + markdown_text_splitter = MarkdownTextSplitter( + chunk_size=max_token_size, chunk_overlap=overlap_token_size + ) + md_splits = markdown_text_splitter.split_documents(md_split_contents) + chunk_order_index = 0 + for md_split in md_splits: + chunk_content = f"{extend_entity_content}{md_split.page_content.strip()}" + chunk_content_size = len(chunk_content) + results.append( + { + "tokens": chunk_content_size, + "content": chunk_content, + "chunk_order_index": chunk_order_index, + } + ) + chunk_order_index += chunk_content_size + return results + + +def _chunking_text_by_markerdown_header_loop( + max_token_size: int, + header_level: int, + content: str +) -> list: + content_splits = [] + header = str("#" * header_level) + header_text = f"Header {header_level}" + markdown_splitter = MarkdownHeaderTextSplitter( + headers_to_split_on=[(header, header_text)], + strip_headers=False, + ) + md_splits = markdown_splitter.split_text(content) + md_loop_splits = _chunking_list_by_markerdown_header_loop( + max_token_size=max_token_size, + header_level=header_level + 1, + contents=md_splits, + ) + content_splits.extend(md_loop_splits) + return content_splits + + +def _chunking_list_by_markerdown_header_loop( + max_token_size: int, + header_level: int, + contents: list +) -> list: + content_splits = [] + for content in contents: + # 循环判断被首次分割的内容,如果超过制定长度,再利用下级标题进行切割 + if len(content.page_content) > max_token_size: + header_title = '' + # 截取content.page_content第一行,判断是否是标题,如果是赋值给header_title + header_line = content.page_content.split("\n")[0] + header_line_is_title = re.match(r"^#{1,6}\s+.+$", header_line) + if header_line_is_title: + header_title = header_line.strip() + + header = str("#" * header_level) + header_text = f"Header {header_level}" + markdown_splitter = MarkdownHeaderTextSplitter( + headers_to_split_on=[(header, header_text)], + strip_headers=False, + ) + md_splits = markdown_splitter.split_text(content.page_content) + for idx, md_split in enumerate(md_splits): + if idx > 0: + md_split.page_content = f"{header_title}\n{md_split.page_content}" + md_loop_splits = _chunking_list_by_markerdown_header_loop( + max_token_size=max_token_size, + header_level=header_level + 1, + contents=md_splits + ) + content_splits.extend(md_loop_splits) + else: + content_splits.append(content) + return content_splits + + +def chunking_by_markdown_text( + content: str, overlap_token_size=128, max_token_size=1024, + extend_entity_title: str = '', + extend_entity_sn: str = '', +): + extend_entity_content = f"{extend_entity_sn} 《{extend_entity_title}》\n" + results = [] + # 用Markdown的文本分割器进行分割 + markdown_splitter = MarkdownTextSplitter( + chunk_size=max_token_size, chunk_overlap=overlap_token_size + ) + md_splits = markdown_splitter.split_text(content) + chunk_order_index = 0 + for md_split in md_splits: + chunk_content = f"{extend_entity_content}{md_split.strip()}" + chunk_content_size = len(chunk_content) + results.append( + { + "tokens": chunk_content_size, + "content": chunk_content, + "chunk_order_index": chunk_order_index, + } + ) + chunk_order_index += chunk_content_size + return results + + async def _handle_entity_relation_summary( entity_or_relation_name: str, description: str, @@ -287,6 +411,10 @@ async def extract_entities( # add example's format examples = examples.format(**example_context_base) + # 自定义新增 主实体编号、名称 by bumaple 2024-12-03 + extend_entity_sn = global_config["extend_entity_sn"] + extend_entity_title = global_config["extend_entity_title"] + entity_extract_prompt = PROMPTS["entity_extraction"] context_base = dict( tuple_delimiter=PROMPTS["DEFAULT_TUPLE_DELIMITER"], @@ -294,6 +422,9 @@ async def extract_entities( completion_delimiter=PROMPTS["DEFAULT_COMPLETION_DELIMITER"], entity_types=",".join(entity_types), examples=examples, + # 自定义新增 主实体编号、名称 by bumaple 2024-12-03 + extend_entity_sn=extend_entity_sn, + extend_entity_title=extend_entity_title, language=language, ) @@ -305,7 +436,8 @@ async def extract_entities( already_relations = 0 async def _user_llm_func_with_cache( - input_text: str, history_messages: list[dict[str, str]] = None + chunk_index: int, chunk_total: int, + input_text: str, history_messages: list[dict[str, str]] = None, ) -> str: if enable_llm_cache_for_entity_extract and llm_response_cache: need_to_restore = False @@ -335,10 +467,10 @@ async def _user_llm_func_with_cache( if history_messages: res: str = await use_llm_func( - input_text, history_messages=history_messages + input_text, history_messages=history_messages, index=index, total=total ) else: - res: str = await use_llm_func(input_text) + res: str = await use_llm_func(input_text, index=index, total=total) await save_to_cache( llm_response_cache, CacheData(args_hash=arg_hash, content=res, prompt=_prompt), @@ -346,11 +478,11 @@ async def _user_llm_func_with_cache( return res if history_messages: - return await use_llm_func(input_text, history_messages=history_messages) + return await use_llm_func(input_text, history_messages=history_messages, index=index, total=total) else: - return await use_llm_func(input_text) + return await use_llm_func(input_text, index=index, total=total) - async def _process_single_content(chunk_key_dp: tuple[str, TextChunkSchema]): + async def _process_single_content(chunk_key_dp: tuple[str, TextChunkSchema], chunk_index: int, chunk_total: int): nonlocal already_processed, already_entities, already_relations chunk_key = chunk_key_dp[0] chunk_dp = chunk_key_dp[1] @@ -360,11 +492,11 @@ async def _process_single_content(chunk_key_dp: tuple[str, TextChunkSchema]): **context_base, input_text="{input_text}" ).format(**context_base, input_text=content) - final_result = await _user_llm_func_with_cache(hint_prompt) + final_result = await _user_llm_func_with_cache(hint_prompt, chunk_index=chunk_index, chunk_total=chunk_total) history = pack_user_ass_to_openai_messages(hint_prompt, final_result) for now_glean_index in range(entity_extract_max_gleaning): glean_result = await _user_llm_func_with_cache( - continue_prompt, history_messages=history + continue_prompt, history_messages=history, chunk_index=chunk_index, chunk_total=chunk_total ) history += pack_user_ass_to_openai_messages(continue_prompt, glean_result) @@ -373,7 +505,7 @@ async def _process_single_content(chunk_key_dp: tuple[str, TextChunkSchema]): break if_loop_result: str = await _user_llm_func_with_cache( - if_loop_prompt, history_messages=history + if_loop_prompt, history_messages=history, chunk_index=chunk_index, chunk_total=chunk_total ) if_loop_result = if_loop_result.strip().strip('"').strip("'").lower() if if_loop_result != "yes": @@ -423,7 +555,7 @@ async def _process_single_content(chunk_key_dp: tuple[str, TextChunkSchema]): results = [] for result in tqdm_async( - asyncio.as_completed([_process_single_content(c) for c in ordered_chunks]), + asyncio.as_completed([_process_single_content(c, idx, len(ordered_chunks)) for idx, c in enumerate(ordered_chunks)]), total=len(ordered_chunks), desc="Extracting entities from chunks", unit="chunk", diff --git a/lightrag/prompt_cn.py b/lightrag/prompt_cn.py new file mode 100644 index 00000000..004fea11 --- /dev/null +++ b/lightrag/prompt_cn.py @@ -0,0 +1,338 @@ +GRAPH_FIELD_SEP = "" + +PROMPTS = {} + +PROMPTS["DEFAULT_LANGUAGE"] = "中文" +PROMPTS["DEFAULT_TUPLE_DELIMITER"] = "<|>" +PROMPTS["DEFAULT_RECORD_DELIMITER"] = "##" +PROMPTS["DEFAULT_COMPLETION_DELIMITER"] = "<|COMPLETE|>" +PROMPTS["process_tickers"] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] + +PROMPTS["DEFAULT_ENTITY_TYPES"] = ["标准元数据", "毒素", "食品", "检测", "限量", "特殊食品", "食品加工", "食品来源", "食品状态", + "食品成分", "容器/包装", "处理方法", "生产工艺", "法规属性", "计量单位", "文档结构", + "食品饮料子", "地域/来源", "营养特征", "加工技术"] + +PROMPTS["entity_extraction"] = """-目的- +给定可能与此活动相关的Markdown或Html文本和一个实体类型列表,从文本中识别出这些类型的所有实体以及所识别实体之间的所有关系,不要遗漏。如果实体内容中存在、号,将、前后内容拆分成不同的实体。 +如果识别出的实体不在给出的实体类型列表中,可定义新的实体类型。提取的内容中html上下标记保留,图片标记保留,按要求的格式输出,不要任何说明与解释。 +使用{language}作为输出语言。 +-步骤- +1.识别所有实体。对于每个已识别的实体,提取以下信息: +-entity_name:实体的名称,使用与输入文本相同的语言。如果是英文,则保留原来的格式。 +-entity_type:实体类型。保留实体类型名称,不需要举例说明内容。 +-entity_description:对实体的属性和活动进行的全面描述。 +将每个实体格式化为 ("entity"{tuple_delimiter}{tuple_delimiter}{tuple_delimiter}){record_delimiter} + +2.从步骤1中识别的实体中,识别彼此*明显相关*的所有对(source_entity、target_entity)。如果有步骤1中识别的实体无*明显相关*的实体,那么建立与"{extend_entity_sn}"实体的“标准与内容"关系。 +对于每对相关实体,提取以下信息: +-source_entity:源实体的名称,如步骤1中所标识的。 +-target_entity:目标实体的名称,如步骤1中所标识的。 +-relationship_description:解释为什么你认为源实体和目标实体是相互关联的。 +-relationship_strength:一个整数分数,表示源实体和目标实体之间关系的强度,0-9之间,9最高,0最低。 +-relationship_keywords:一个或多个高级关键词,总结关系的总体性质,侧重于概念或主题而非具体细节,关键字之间用、分隔。 +将每个关系格式化为 ("relationship"{tuple_delimiter}{tuple_delimiter}{tuple_delimiter}{tuple_delimiter}{tuple_delimiter}){record_delimiter} + +3.识别能够概括整个文本主要概念、主题或话题的高级关键词。这些关键词应捕捉文档中呈现的总体思想。关键词中的html上下标记保留。 +将内容级关键字格式化为 ("content_keywords"{tuple_delimiter}) + +4.以{language}返回输出,作为步骤1和2中标识的所有实体和关系的单个列表。 + +5.完成后,输出{completion_delimiter} + +-实体类型列表- +entity_types: [{entity_types}] +############################# +-范例- +###################### +{examples} + +############################# +-待处理的数据- +###################### +文本内容: {input_text} +###################### +输出: +""" + +PROMPTS["entity_extraction_examples"] = [ + """范例1: +文本内容: +中华人民共和国国家标准 +GB2761-2017 +食品安全国家标准 +食品中真菌毒素限量 +2017-03-17发布 +2017-09-17实施 +中华人民共和国国家卫生和计划生育委员会 发布 +# 前言 +GB2761-2017代替GB2761-2011《食品安全国家标准食品中真菌毒素限量》。 +GB2761-2017与GB2761-2011相比,主要变化如下: +--增加了葡萄酒和咖啡中赭曲霉毒素A限量要求; +--更新了检验方法标准号; +食品安全国家标准食品中真菌毒素限量 +################ +输出: +("entity"{tuple_delimiter}GB2761-2017{tuple_delimiter}标准编号{tuple_delimiter}食品安全国家标准食品中真菌毒素限量的标准编号){record_delimiter} +("entity"{tuple_delimiter}食品安全国家标准{tuple_delimiter}标准名称{tuple_delimiter}规范食品中真菌毒素限量的国家标准){record_delimiter} +("entity"{tuple_delimiter}食品中真菌毒素限量{tuple_delimiter}标准类别{tuple_delimiter}食品安全国家标准的具体类别){record_delimiter} +("entity"{tuple_delimiter}2017-03-17{tuple_delimiter}发布日期{tuple_delimiter}标准发布的具体日期){record_delimiter} +("entity"{tuple_delimiter}2017-09-17{tuple_delimiter}实施日期{tuple_delimiter}标准正式实施的日期){record_delimiter} +("entity"{tuple_delimiter}中华人民共和国国家卫生和计划生育委员会{tuple_delimiter}发布机构{tuple_delimiter}负责发布该标准的政府机构之一){record_delimiter} +("entity"{tuple_delimiter}GB2761-2011{tuple_delimiter}代替标准{tuple_delimiter}被GB2761-2017替代的旧标准){record_delimiter} +("entity"{tuple_delimiter}葡萄酒{tuple_delimiter}食品名称{tuple_delimiter}新增赭曲霉毒素A限量要求的食品){record_delimiter} +("entity"{tuple_delimiter}咖啡{tuple_delimiter}食品名称{tuple_delimiter}新增赭曲霉毒素A限量要求的食品){record_delimiter} +("entity"{tuple_delimiter}赭曲霉毒素A{tuple_delimiter}元素{tuple_delimiter}在葡萄酒和咖啡中新增限量要求的真菌毒素){record_delimiter} +("entity"{tuple_delimiter}检验方法标准号{tuple_delimiter}检验方法标准号{tuple_delimiter}更新的检验方法相关标准号){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}食品安全国家标准{tuple_delimiter}国家标准文件描述了食品中真菌毒素限量的具体标准{tuple_delimiter}标准、规范、内容{tuple_delimiter}8){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}2017-03-17{tuple_delimiter}标准的发布日期{tuple_delimiter}发布、时间{tuple_delimiter}7){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}2017-09-17{tuple_delimiter}标准的实施日期{tuple_delimiter}实施、时间{tuple_delimiter}7){record_delimiter} +("relationship"{tuple_delimiter}中华人民共和国国家卫生和计划生育委员会{tuple_delimiter}GB2761-2017{tuple_delimiter}作为标准发布机构之一{tuple_delimiter}发布者、标准{tuple_delimiter}6){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}GB2761-2011{tuple_delimiter}新标准替代旧标准{tuple_delimiter}代替、更新{tuple_delimiter}8){record_delimiter} +("relationship"{tuple_delimiter}葡萄酒{tuple_delimiter}赭曲霉毒素A{tuple_delimiter}新增限量要求的食品类型和元素{tuple_delimiter}限量、增加{tuple_delimiter}7){record_delimiter} +("relationship"{tuple_delimiter}咖啡{tuple_delimiter}赭曲霉毒素A{tuple_delimiter}新增限量要求的食品类型和元素{tuple_delimiter}限量、增加{tuple_delimiter}7){record_delimiter} +("content_keywords"{tuple_delimiter}食品安全、真菌毒素、国家标准、限量要求、营养食品、检验方法、发布日期、实施日期、发布机构){completion_delimiter} +######################""", + """范例2: +文本内容: +GB2761-2017 食品中真菌毒素限量 +# 1 范围 +GB2761-2017规定了食品中黄曲霉毒素B1、黄曲霉毒素M1、脱氧雪腐镰刀菌烯醇、展青霉素、赭曲霉毒素A及玉米赤霉烯酮的限量指标。 +# 2 术语和定义 +## 2.1 真菌毒素 +真菌在生长繁殖过程中产生的次生有毒代谢产物。 +## 2.2 可食用部分 +食品原料经过机械手段(如谷物碾磨、水果剥皮、坚果去壳、肉去骨、鱼去刺、贝去壳等)去除非食用部分后,所得到的用于食用的部分。 +注1:非食用部分的去除不可采用任何非机械手段(如粗制植物油精炼过程)。 +注2:用相同的食品原料生产不同产品时,可食用部分的量依生产工艺不同而异。如用麦类加工麦片和全麦粉时,可食用部分按100%计算;加工小麦粉时,可食用部分按出粉率折算。 +## 2.3 限量 +真菌毒素在食品原料和(或)食品成品可食用部分中允许的最大含量水平。 +# 3 应用原则 +## 3.1 无论是否制定真菌毒素限量,食品生产和加工者均应采取控制措施,使食品中真菌毒素的含量达到最低水平。 +## 3.2 GB2761-2017列出了可能对公众健康构成较大风险的真菌毒素,制定限量值的食品是对消费者膳食暴露量产生较大影响的食品。 +## 3.3 食品类别(名称)说明(附录A)用于界定真菌毒素限量的适用范围,仅适用于GB2761-2017。当某种真菌毒素限量应用于某一食品类别(名称)时,则该食品类别(名称)内的所有类别食品均适用,有特别规定的除外。 +## 3.4 食品中真菌毒素限量以食品通常的可食用部分计算,有特别规定的除外。 +############# +输出: +("entity"{tuple_delimiter}GB2761-2017{tuple_delimiter}标准号{tuple_delimiter}食品中真菌毒素限量的国家标准编号,2017年版本){record_delimiter} +("entity"{tuple_delimiter}食品中真菌毒素限量{tuple_delimiter}标准名称{tuple_delimiter}规定食品中真菌毒素最大允许含量的国家标准){record_delimiter} +("entity"{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}元素{tuple_delimiter}一种由黄曲霉菌产生的有毒代谢产物){record_delimiter} +("entity"{tuple_delimiter}黄曲霉毒素M1{tuple_delimiter}元素{tuple_delimiter}黄曲霉毒素的一种变体,常见于奶制品){record_delimiter} +("entity"{tuple_delimiter}脱氧雪腐镰刀菌烯醇{tuple_delimiter}化合物{tuple_delimiter}一种由镰刀菌产生的真菌毒素){record_delimiter} +("entity"{tuple_delimiter}展青霉素{tuple_delimiter}化合物{tuple_delimiter}一种由青霉菌产生的真菌毒素){record_delimiter} +("entity"{tuple_delimiter}赭曲霉毒素A{tuple_delimiter}化合物{tuple_delimiter}一种由曲霉菌产生的霉菌毒素){record_delimiter} +("entity"{tuple_delimiter}玉米赤霉烯酮{tuple_delimiter}化合物{tuple_delimiter}一种由赤霉菌在玉米上产生的真菌毒素){record_delimiter} +("entity"{tuple_delimiter}真菌毒素{tuple_delimiter}术语{tuple_delimiter}真菌在生长繁殖过程中产生的次生有毒代谢产物){record_delimiter} +("entity"{tuple_delimiter}可食用部分{tuple_delimiter}术语{tuple_delimiter}食品原料经过机械手段去除非食用部分后,用于食用的部分){record_delimiter} +("entity"{tuple_delimiter}限量{tuple_delimiter}术语{tuple_delimiter}真菌毒素在食品原料和(或)食品成品可食用部分中允许的最大含量水平){record_delimiter} +("entity"{tuple_delimiter}3.1{tuple_delimiter}应用原则{tuple_delimiter}食品生产和加工者应采取控制措施,使食品中真菌毒素含量达到最低水平){record_delimiter} +("entity"{tuple_delimiter}3.2{tuple_delimiter}应用原则{tuple_delimiter}标准列出了可能对公众健康构成较大风险的真菌毒素,并制定限量值的食品){record_delimiter} +("entity"{tuple_delimiter}3.3{tuple_delimiter}应用原则{tuple_delimiter}食品类别说明用于界定真菌毒素限量的适用范围,适用于标准中的所有食品类别){record_delimiter} +("entity"{tuple_delimiter}3.4{tuple_delimiter}应用原则{tuple_delimiter}食品中真菌毒素限量以食品通常的可食用部分计算,特别规定除外){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}标准规定了该毒素在食品中的最大允许限量{tuple_delimiter}规定{tuple_delimiter}7){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}黄曲霉毒素M1{tuple_delimiter}标准规定了该毒素在食品中的最大允许限量{tuple_delimiter}规定{tuple_delimiter}7){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}脱氧雪腐镰刀菌烯醇{tuple_delimiter}标准规定了该毒素在食品中的最大允许限量{tuple_delimiter}规定{tuple_delimiter}7){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}展青霉素{tuple_delimiter}标准规定了该毒素在食品中的最大允许限量{tuple_delimiter}规定{tuple_delimiter}7){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}赭曲霉毒素A{tuple_delimiter}标准规定了该毒素在食品中的最大允许限量{tuple_delimiter}规定{tuple_delimiter}7){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}玉米赤霉烯酮{tuple_delimiter}标准规定了该毒素在食品中的最大允许限量{tuple_delimiter}规定{tuple_delimiter}7){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}可食用部分{tuple_delimiter}标准规定了食品原料经过机械手段(如谷物碾磨、水果剥皮、坚果去壳、肉去骨、鱼去刺、贝去壳等)去除非食用部分后,所得到的用于食用的部分{tuple_delimiter}规定{tuple_delimiter}6){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}真菌毒素{tuple_delimiter}标准规定了真菌毒素在食品原料和(或)食品成品可食用部分中的最大允许含量。{tuple_delimiter}含量限制{tuple_delimiter}4){record_delimiter} +("relationship"{tuple_delimiter}GB2761-2017{tuple_delimiter}应用原则{tuple_delimiter}标准规定了应用原则{tuple_delimiter}适用{tuple_delimiter}5){record_delimiter} +("content_keywords"{tuple_delimiter}真菌毒素、食品安全、限量标准、毒素类型、可食用部分、应用原则、控制措施){completion_delimiter} +######################""", + """范例3: +文本内容: +GB2761-2017 食品中真菌毒素限量 +# 4 指标要求 +## 4.1 黄曲霉毒素B1 +### 4.1.1 食品中黄曲霉毒素B1限量指标见表1.。 +表1. 食品中黄曲霉毒素B1限量指标 +
食品类别(名称)限量μg/kg
一级类别二级类别
乳及乳制品/0.5
特殊膳食用食品婴幼儿配方食品--婴儿配方食品b0.5(以粉状产品计)
婴幼儿配方食品--较大婴儿和幼儿配方食品b0.5(以粉状产品计)
婴幼儿配方食品--特殊医学用途婴儿配方食品0.5(以粉状产品计)
特殊医学用途配方食品b(特殊医学用途婴儿配方食品涉及的品种除外)0.5(以固态产品计)
辅食营养补充品c0.5
运动营养食品b0.5
孕妇及乳母营养补充食品c0.5
a乳粉按生乳折算。
b以乳类及乳蛋白制品为主要原料的产品。
c只限于含乳类的产品。
+### 4.1.2 检验方法:按GB5009.22规定的方法测定。 +############# +输出: +("entity"{tuple_delimiter}GB2761-2017{tuple_delimiter}标准号{tuple_delimiter}食品中真菌毒素限量的国家标准编号,2017年版本){record_delimiter} +("entity"{tuple_delimiter}食品中真菌毒素限量{tuple_delimiter}标准名称{tuple_delimiter}规定食品中真菌毒素最大允许含量的国家标准){record_delimiter} +("entity"{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}指标{tuple_delimiter}食品中黄曲霉毒素B1的最大允许含量水平){record_delimiter} +("entity"{tuple_delimiter}食品类别(名称){tuple_delimiter}范围{tuple_delimiter}列出不同食品类别的黄曲霉毒素B1限量指标){record_delimiter} +("entity"{tuple_delimiter}乳及乳制品{tuple_delimiter}食品类别{tuple_delimiter}包含乳制品在内的食品类别){record_delimiter} +("entity"{tuple_delimiter}特殊膳食用食品{tuple_delimiter}食品类别{tuple_delimiter}专为特定人群设计的膳食食品){record_delimiter} +("entity"{tuple_delimiter}婴幼儿配方食品--婴儿配方食品{tuple_delimiter}食品类别{tuple_delimiter}为婴儿设计的配方食品){record_delimiter} +("entity"{tuple_delimiter}婴幼儿配方食品--较大婴儿和幼儿配方食品{tuple_delimiter}食品类别{tuple_delimiter}为较大婴儿和幼儿设计的配方食品){record_delimiter} +("entity"{tuple_delimiter}婴幼儿配方食品--特殊医学用途婴儿配方食品{tuple_delimiter}食品类别{tuple_delimiter}特殊医学用途婴儿配方食品){record_delimiter} +("entity"{tuple_delimiter}特殊医学用途配方食品{tuple_delimiter}食品类别{tuple_delimiter}为特定医疗需求人群设计的食品){record_delimiter} +("entity"{tuple_delimiter}辅食营养补充品{tuple_delimiter}食品类别{tuple_delimiter}用于补充婴幼儿营养的食品产品){record_delimiter} +("entity"{tuple_delimiter}运动营养食品{tuple_delimiter}食品类别{tuple_delimiter}专门为运动员和运动爱好者设计的营养食品){record_delimiter} +("entity"{tuple_delimiter}孕妇及乳母营养补充食品{tuple_delimiter}食品类别{tuple_delimiter}为孕妇和哺乳期妇女设计的营养补充食品){record_delimiter} +("entity"{tuple_delimiter}GB5009.22{tuple_delimiter}检验方法标准号{tuple_delimiter}描述检测食品中黄曲霉毒素B1的方法标准号){record_delimiter} +("entity"{tuple_delimiter}0.5{tuple_delimiter}数值{tuple_delimiter}黄曲霉毒素B1的限量指标值){record_delimiter} +("entity"{tuple_delimiter}μg/kg{tuple_delimiter}计量单位{tuple_delimiter}黄曲霉毒素B1限量的计量单位){record_delimiter} +("relationship"{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}乳及乳制品{tuple_delimiter}标准规定乳及乳制品中黄曲霉毒素B1的限量为0.5μg/kg{tuple_delimiter}规定{tuple_delimiter}6){record_delimiter} +("relationship"{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}特殊膳食用食品{tuple_delimiter}标准规定特殊膳食用食品中黄曲霉毒素B1的限量为0.5μg/kg{tuple_delimiter}规定{tuple_delimiter}6){record_delimiter} +("relationship"{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}婴幼儿配方食品--婴儿配方食品{tuple_delimiter}婴儿配方食品中的黄曲霉毒素B1限量为0.5μg/kg{tuple_delimiter}规定{tuple_delimiter}6){record_delimiter} +("relationship"{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}婴幼儿配方食品--较大婴儿和幼儿配方食品{tuple_delimiter}较大婴儿和幼儿配方食品中的黄曲霉毒素B1限量为0.5μg/kg{tuple_delimiter}规定{tuple_delimiter}6){record_delimiter} +("relationship"{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}婴幼儿配方食品--特殊医学用途婴儿配方食品{tuple_delimiter}特殊医学用途婴儿配方食品中的黄曲霉毒素B1限量为0.5μg/kg{tuple_delimiter}规定{tuple_delimiter}6){record_delimiter} +("relationship"{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}特殊医学用途配方食品{tuple_delimiter}特殊医学用途配方食品中的黄曲霉毒素B1限量为0.5μg/kg{tuple_delimiter}规定{tuple_delimiter}6){record_delimiter} +("relationship"{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}辅食营养补充品{tuple_delimiter}辅食营养补充品中的黄曲霉毒素B1限量为0.5μg/kg{tuple_delimiter}规定{tuple_delimiter}6){record_delimiter} +("relationship"{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}运动营养食品{tuple_delimiter}运动营养食品中的黄曲霉毒素B1限量为0.5μg/kg{tuple_delimiter}规定{tuple_delimiter}6){record_delimiter} +("relationship"{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}孕妇及乳母营养补充食品{tuple_delimiter}孕妇及乳母营养补充食品中的黄曲霉毒素B1限量为0.5μg/kg{tuple_delimiter}规定{tuple_delimiter}6){record_delimiter} +("relationship"{tuple_delimiter}黄曲霉毒素B1{tuple_delimiter}GB5009.22{tuple_delimiter}检验方法标准号GB5009.22用于测定黄曲霉毒素B1含量{tuple_delimiter}测定{tuple_delimiter}7){record_delimiter} +("content_keywords"{tuple_delimiter}黄曲霉毒素B1、食品类别、营养食品、限量标准、检验方法){completion_delimiter} +######################""", +] + +PROMPTS[ + "summarize_entity_descriptions" +] = """你是一个负责生成综合摘要的助手,按要求处理提供的数据:给定一个或两个实体,以及一系列与这些实体或实体组相关的描述。请将所有这些描述合并成一个综合描述,确保包含从所有描述中收集到的信息。 +如果提供的描述存在矛盾之处,请解决这些矛盾,并提供一个单一且连贯的摘要。 +请确保以第三人称撰写,并包含实体名称,以便我们了解完整的上下文。按要求的格式输出,不要任何说明与解释。 +使用{language}作为输出语言。 +####### +-待处理数据- +实体: {entity_name} +相关描述: {description_list} +####### +输出: +""" + +PROMPTS[ + "entiti_continue_extraction" +] = """在上次提取中遗漏了许多实体。请使用相同的格式将它们添加在下面: +""" + +PROMPTS[ + "entiti_if_loop_extraction" +] = """似乎仍有一些实体可能被遗漏了。如果仍有需要添加的实体,请回答“YES”;如果没有,请回答“NO”。回答不要包含“YES”、“NO”之外的任何内容。 +""" + +PROMPTS["fail_response"] = "抱歉,我无法回答这个问题。" + +PROMPTS["rag_response"] = """---角色--- +你是一个负责回答有关所提供表格中数据的问题的助手。 +---目的--- +生成一个符合目标长度和格式的回复,以回答用户的问题。回复应总结输入数据表格中所有适合回复长度和格式的信息,并融入任何相关的常识性知识。 +如果你不知道答案,就直接说出来。不要编造任何内容。 +不要包含没有提供支持性证据的信息。 +---目标回复长度和格式--- +{response_type} +---表格数据--- +{context_data} +根据回复的长度和格式要求,适当添加章节和评论。以markdown格式对回复进行排版。 +""" + +PROMPTS["keywords_extraction"] = """---角色--- +你是一个负责识别用户查询中的高级和低级关键词的助手。 +使用{language}作为输出语言。 +---目的--- +根据查询,列出高级和低级关键词。高级关键词关注总体概念或主题,而低级关键词关注具体实体、细节或具体术语。 +---指令--- +- 以JSON格式输出关键词。 +- JSON应包含两个键: + - "high_level_keywords" 高级关键词,用于表示总体概念或主题。 + - "low_level_keywords" 低级关键词,用于表示具体实体或细节。 +############################# +-范例- +###################### +{examples} + +############################# +-待处理的数据- +###################### +查询: {query} +###################### +“输出”应该是人类文本,而不是unicode字符。保持与“查询”相同的语言。 +输出: +""" + +PROMPTS["keywords_extraction_examples"] = [ + """范例1: +查询: "国际贸易如何影响全球经济稳定?" +################ +输出: +{{ + "high_level_keywords": ["国际贸易", "影响", "全球经济", "经济稳定"], + "low_level_keywords": ["经济增长", "国内生产总值(GDP)", "就业率", "技术创新", "产业升级", "资源配置", "市场竞争力", "企业盈利能力", "市场规模"] +}} +######################""", + """范例2: +查询: "森林砍伐对生物多样性的环境影响是什么?" +################ +输出: +{{ + "high_level_keywords": ["森林砍伐", "生物多样性", "环境影响"], + "low_level_keywords": ["栖息地丧失", "物种灭绝", "生态系统破坏", "土壤侵蚀", "气候变化", "碳排放", "水源保护", "植被覆盖"] +}} +######################""", + """范例3: +查询: "教育在减少贫困中的作用是什么?" +################ +输出: +{{ + "high_level_keywords": ["教育", "减少贫困", "作用"], + "low_level_keywords": ["就业机会", "收入水平", "技能提升", "社会流动性", "健康意识", "儿童福利", "社区发展", "经济贡献"] +}} +#############################""", +] + +PROMPTS["naive_rag_response"] = """---角色--- +你是一个负责回答有关所提供文档的问题的助手。 +---目的--- +生成一个符合目标长度和格式的回复来回答用户的问题,回复中应总结输入文档内容中适合回复长度和格式的所有信息,并融入任何相关的常识性知识。 +如果你不知道答案,就直接说出来,不要编造任何内容。 +不要包含没有提供支持性证据的信息。 +---目标回复长度和格式--- +{response_type} +---文档内容--- +{content_data} +根据回复的长度和格式要求,适当添加章节和评论。使用markdown格式对回复进行排版。 +""" + +PROMPTS[ + "similarity_check" +] = """请分析这两个问题之间的相似性: + +问题1:{original_prompt} +问题2:{cached_prompt} + +请评估以下两点,并直接提供0到1之间的相似性得分: +1.这两个问题在语义上是否相似; +2.“问题2”的答案是否可用于回答“问题1”。 +相似性评分标准: +0:完全无关或答案不能重复使用,包括但不限于: + -这些问题有不同的主题。 + -问题中提到的地点不同。 + -问题中提到的时间不同。 + -问题中提到的具体个人不同。 + -问题中提到的具体事件不同。 + -问题中的背景信息不同。 + -问题中的关键条件不同。 + -问题中提到的标准名称、标准编号、标准号不同。 +1:完全相同,答案可以直接重复使用 +0.5:部分相关,需要修改答案才能使用 +仅返回0-1之间的数字,不包含任何其他内容。 +""" + +PROMPTS["mix_rag_response"] = """---角色--- +您是一名专业助理,负责根据知识图谱和文本信息回答问题。请用与用户问题相同的语言回答。 +---目的--- +生成一个简洁的回复,总结所提供信息中的相关要点。如果你不知道答案,就直接说出来。不要编造任何东西,也不要在没有证据支持的情况下提供回复。 +处理带有时间戳的信息时: +1.每条信息(实体、关系和内容)都有一个“created_at”时间戳,表示我们何时获得了这些知识 +2.遇到冲突信息时,请同时考虑内容/实体/关系和时间戳 +3.不要自动选择最新的信息——根据上下文进行判断 +4.对于特定时间的查询,在考虑创建时间戳之前,优先考虑内容中的时间信息 +---数据来源--- +1.知识图谱数据: +{kg_context} +2.矢量数据: +{vector_context} +---响应要求--- +-目标格式和长度:{response_type} +-使用带有适当章节标题的markdown格式 +-为了保持简洁,内容应保持在3段左右 +-每一段都应在相关章节标题下 +-每个部分都应该关注答案的一个要点或方面 +-使用清晰、描述性的章节标题来反映内容 +-在“参考文献”下的末尾列出最多5个最重要的参考来源,明确指出每个来源是来自知识图谱还是文本数据 +格式:[知识图谱/文本数据]源内容 +根据长度和格式,在回复中添加适当的章节和评论。如果提供的信息不足以回答问题,请明确表示您不知道或无法以与用户问题相同的语言提供答案。 +""" \ No newline at end of file diff --git a/lightrag/utils.py b/lightrag/utils.py index 1f6bf405..6dc9690c 100644 --- a/lightrag/utils.py +++ b/lightrag/utils.py @@ -14,6 +14,7 @@ import numpy as np import tiktoken +import loguru from lightrag.prompt import PROMPTS @@ -30,22 +31,36 @@ async def __aexit__(self, exc_type, exc, tb): ENCODER = None -logger = logging.getLogger("lightrag") - - -def set_logger(log_file: str): - logger.setLevel(logging.DEBUG) - - file_handler = logging.FileHandler(log_file) - file_handler.setLevel(logging.DEBUG) - - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) - file_handler.setFormatter(formatter) +# logger = logging.getLogger("lightrag") +logger = loguru.logger + +# def set_logger(log_file: str): +# logger.setLevel(logging.DEBUG) +# +# file_handler = logging.FileHandler(log_file) +# file_handler.setLevel(logging.DEBUG) +# +# formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +# file_handler.setFormatter(formatter) +# +# if not logger.handlers: +# logger.addHandler(file_handler) + +def set_logger(log_file: str, log_level: str = "INFO"): + """ + TRACE:用于追踪代码中的详细信息。 + DEBUG:用于调试和开发过程中的详细信息。 + INFO:用于提供一般性的信息,表明应用程序正在按预期运行。 + SUCCESS:用于表示成功完成的操作。 + WARNING:用于表示潜在的问题或警告,不会导致应用程序的中断或错误。 + ERROR:用于表示错误,可能会导致应用程序的中断或异常行为。 + CRITICAL:用于表示严重错误,通常与应用程序无法继续执行相关。 + """ + if log_level not in ['TRACE', 'DEBUG', 'INFO', 'SUCCESS', 'WARNING', 'ERROR', 'CRITICAL']: + log_level = 'INFO' - if not logger.handlers: - logger.addHandler(file_handler) + logger.add(sink=log_file, level=log_level, rotation="1 days", retention="30 days", + encoding="utf-8", enqueue=True, colorize=False, backtrace=True, diagnose=True) @dataclass @@ -91,7 +106,6 @@ def locate_json_string_body_from_string(content: str) -> Union[str, None]: return None - def convert_response_to_json(response: str) -> dict: json_str = locate_json_string_body_from_string(response) assert json_str is not None, f"Unable to parse JSON from response: {response}" @@ -229,7 +243,7 @@ def csv_string_to_list(csv_string: str) -> List[List[str]]: def save_data_to_file(data, file_name): - with open(file_name, "w", encoding="utf-8") as f: + with open(file_name, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=4)