Merge branch 'main' into neo4j-add-min-degree
This commit is contained in:
@@ -37,8 +37,8 @@ async def llm_model_func(
|
|||||||
prompt,
|
prompt,
|
||||||
system_prompt=system_prompt,
|
system_prompt=system_prompt,
|
||||||
history_messages=history_messages,
|
history_messages=history_messages,
|
||||||
api_key="sk-91d0b59f25554251aa813ed756d79a6d",
|
api_key="",
|
||||||
base_url="https://api.deepseek.com",
|
base_url="",
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
51
examples/test_postgres.py
Normal file
51
examples/test_postgres.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
from lightrag.kg.postgres_impl import PGGraphStorage
|
||||||
|
from lightrag.llm.ollama import ollama_embedding
|
||||||
|
from lightrag.utils import EmbeddingFunc
|
||||||
|
|
||||||
|
#########
|
||||||
|
# Uncomment the below two lines if running in a jupyter notebook to handle the async nature of rag.insert()
|
||||||
|
# import nest_asyncio
|
||||||
|
# nest_asyncio.apply()
|
||||||
|
#########
|
||||||
|
|
||||||
|
WORKING_DIR = "./local_neo4jWorkDir"
|
||||||
|
|
||||||
|
if not os.path.exists(WORKING_DIR):
|
||||||
|
os.mkdir(WORKING_DIR)
|
||||||
|
|
||||||
|
# AGE
|
||||||
|
os.environ["AGE_GRAPH_NAME"] = "dickens"
|
||||||
|
|
||||||
|
os.environ["POSTGRES_HOST"] = "localhost"
|
||||||
|
os.environ["POSTGRES_PORT"] = "15432"
|
||||||
|
os.environ["POSTGRES_USER"] = "rag"
|
||||||
|
os.environ["POSTGRES_PASSWORD"] = "rag"
|
||||||
|
os.environ["POSTGRES_DATABASE"] = "rag"
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
graph_db = PGGraphStorage(
|
||||||
|
namespace="dickens",
|
||||||
|
embedding_func=EmbeddingFunc(
|
||||||
|
embedding_dim=1024,
|
||||||
|
max_token_size=8192,
|
||||||
|
func=lambda texts: ollama_embedding(
|
||||||
|
texts, embed_model="bge-m3", host="http://localhost:11434"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
global_config={},
|
||||||
|
)
|
||||||
|
await graph_db.initialize()
|
||||||
|
labels = await graph_db.get_all_labels()
|
||||||
|
print("all labels", labels)
|
||||||
|
|
||||||
|
res = await graph_db.get_knowledge_graph("FEZZIWIG")
|
||||||
|
print("knowledge graphs", res)
|
||||||
|
|
||||||
|
await graph_db.finalize()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
@@ -229,3 +229,43 @@ class ChromaVectorDBStorage(BaseVectorStorage):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error while deleting vectors from {self.namespace}: {e}")
|
logger.error(f"Error while deleting vectors from {self.namespace}: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
async def search_by_prefix(self, prefix: str) -> list[dict[str, Any]]:
|
||||||
|
"""Search for records with IDs starting with a specific prefix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix: The prefix to search for in record IDs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of records with matching ID prefixes
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get all records from the collection
|
||||||
|
# Since ChromaDB doesn't directly support prefix search on IDs,
|
||||||
|
# we'll get all records and filter in Python
|
||||||
|
results = self._collection.get(
|
||||||
|
include=["metadatas", "documents", "embeddings"]
|
||||||
|
)
|
||||||
|
|
||||||
|
matching_records = []
|
||||||
|
|
||||||
|
# Filter records where ID starts with the prefix
|
||||||
|
for i, record_id in enumerate(results["ids"]):
|
||||||
|
if record_id.startswith(prefix):
|
||||||
|
matching_records.append(
|
||||||
|
{
|
||||||
|
"id": record_id,
|
||||||
|
"content": results["documents"][i],
|
||||||
|
"vector": results["embeddings"][i],
|
||||||
|
**results["metadatas"][i],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Found {len(matching_records)} records with prefix '{prefix}'"
|
||||||
|
)
|
||||||
|
return matching_records
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during prefix search in ChromaDB: {str(e)}")
|
||||||
|
raise
|
||||||
|
@@ -371,3 +371,24 @@ class FaissVectorDBStorage(BaseVectorStorage):
|
|||||||
return False # Return error
|
return False # Return error
|
||||||
|
|
||||||
return True # Return success
|
return True # Return success
|
||||||
|
|
||||||
|
async def search_by_prefix(self, prefix: str) -> list[dict[str, Any]]:
|
||||||
|
"""Search for records with IDs starting with a specific prefix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix: The prefix to search for in record IDs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of records with matching ID prefixes
|
||||||
|
"""
|
||||||
|
matching_records = []
|
||||||
|
|
||||||
|
# Search for records with IDs starting with the prefix
|
||||||
|
for faiss_id, meta in self._id_to_meta.items():
|
||||||
|
if "__id__" in meta and meta["__id__"].startswith(prefix):
|
||||||
|
# Create a copy of all metadata and add "id" field
|
||||||
|
record = {**meta, "id": meta["__id__"]}
|
||||||
|
matching_records.append(record)
|
||||||
|
|
||||||
|
logger.debug(f"Found {len(matching_records)} records with prefix '{prefix}'")
|
||||||
|
return matching_records
|
||||||
|
@@ -206,3 +206,28 @@ class MilvusVectorDBStorage(BaseVectorStorage):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error while deleting vectors from {self.namespace}: {e}")
|
logger.error(f"Error while deleting vectors from {self.namespace}: {e}")
|
||||||
|
|
||||||
|
async def search_by_prefix(self, prefix: str) -> list[dict[str, Any]]:
|
||||||
|
"""Search for records with IDs starting with a specific prefix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix: The prefix to search for in record IDs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of records with matching ID prefixes
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Use Milvus query with expression to find IDs with the given prefix
|
||||||
|
expression = f'id like "{prefix}%"'
|
||||||
|
results = self._client.query(
|
||||||
|
collection_name=self.namespace,
|
||||||
|
filter=expression,
|
||||||
|
output_fields=list(self.meta_fields) + ["id"],
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(f"Found {len(results)} records with prefix '{prefix}'")
|
||||||
|
return results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error searching for records with prefix '{prefix}': {e}")
|
||||||
|
return []
|
||||||
|
@@ -1045,6 +1045,32 @@ class MongoVectorDBStorage(BaseVectorStorage):
|
|||||||
except PyMongoError as e:
|
except PyMongoError as e:
|
||||||
logger.error(f"Error deleting relations for {entity_name}: {str(e)}")
|
logger.error(f"Error deleting relations for {entity_name}: {str(e)}")
|
||||||
|
|
||||||
|
async def search_by_prefix(self, prefix: str) -> list[dict[str, Any]]:
|
||||||
|
"""Search for records with IDs starting with a specific prefix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix: The prefix to search for in record IDs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of records with matching ID prefixes
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Use MongoDB regex to find documents where _id starts with the prefix
|
||||||
|
cursor = self._data.find({"_id": {"$regex": f"^{prefix}"}})
|
||||||
|
matching_records = await cursor.to_list(length=None)
|
||||||
|
|
||||||
|
# Format results
|
||||||
|
results = [{**doc, "id": doc["_id"]} for doc in matching_records]
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Found {len(results)} records with prefix '{prefix}' in {self.namespace}"
|
||||||
|
)
|
||||||
|
return results
|
||||||
|
|
||||||
|
except PyMongoError as e:
|
||||||
|
logger.error(f"Error searching by prefix in {self.namespace}: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
async def get_or_create_collection(db: AsyncIOMotorDatabase, collection_name: str):
|
async def get_or_create_collection(db: AsyncIOMotorDatabase, collection_name: str):
|
||||||
collection_names = await db.list_collection_names()
|
collection_names = await db.list_collection_names()
|
||||||
|
@@ -236,3 +236,23 @@ class NanoVectorDBStorage(BaseVectorStorage):
|
|||||||
return False # Return error
|
return False # Return error
|
||||||
|
|
||||||
return True # Return success
|
return True # Return success
|
||||||
|
|
||||||
|
async def search_by_prefix(self, prefix: str) -> list[dict[str, Any]]:
|
||||||
|
"""Search for records with IDs starting with a specific prefix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix: The prefix to search for in record IDs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of records with matching ID prefixes
|
||||||
|
"""
|
||||||
|
storage = await self.client_storage
|
||||||
|
matching_records = []
|
||||||
|
|
||||||
|
# Search for records with IDs starting with the prefix
|
||||||
|
for record in storage["data"]:
|
||||||
|
if "__id__" in record and record["__id__"].startswith(prefix):
|
||||||
|
matching_records.append({**record, "id": record["__id__"]})
|
||||||
|
|
||||||
|
logger.debug(f"Found {len(matching_records)} records with prefix '{prefix}'")
|
||||||
|
return matching_records
|
||||||
|
@@ -494,6 +494,41 @@ class OracleVectorDBStorage(BaseVectorStorage):
|
|||||||
logger.error(f"Error deleting relations for entity {entity_name}: {e}")
|
logger.error(f"Error deleting relations for entity {entity_name}: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
async def search_by_prefix(self, prefix: str) -> list[dict[str, Any]]:
|
||||||
|
"""Search for records with IDs starting with a specific prefix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix: The prefix to search for in record IDs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of records with matching ID prefixes
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Determine the appropriate table based on namespace
|
||||||
|
table_name = namespace_to_table_name(self.namespace)
|
||||||
|
|
||||||
|
# Create SQL query to find records with IDs starting with prefix
|
||||||
|
search_sql = f"""
|
||||||
|
SELECT * FROM {table_name}
|
||||||
|
WHERE workspace = :workspace
|
||||||
|
AND id LIKE :prefix_pattern
|
||||||
|
ORDER BY id
|
||||||
|
"""
|
||||||
|
|
||||||
|
params = {"workspace": self.db.workspace, "prefix_pattern": f"{prefix}%"}
|
||||||
|
|
||||||
|
# Execute query and get results
|
||||||
|
results = await self.db.query(search_sql, params, multirows=True)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Found {len(results) if results else 0} records with prefix '{prefix}'"
|
||||||
|
)
|
||||||
|
return results or []
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error searching records with prefix '{prefix}': {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@@ -575,6 +575,41 @@ class PGVectorStorage(BaseVectorStorage):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error deleting relations for entity {entity_name}: {e}")
|
logger.error(f"Error deleting relations for entity {entity_name}: {e}")
|
||||||
|
|
||||||
|
async def search_by_prefix(self, prefix: str) -> list[dict[str, Any]]:
|
||||||
|
"""Search for records with IDs starting with a specific prefix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix: The prefix to search for in record IDs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of records with matching ID prefixes
|
||||||
|
"""
|
||||||
|
table_name = namespace_to_table_name(self.namespace)
|
||||||
|
if not table_name:
|
||||||
|
logger.error(f"Unknown namespace for prefix search: {self.namespace}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
search_sql = f"SELECT * FROM {table_name} WHERE workspace=$1 AND id LIKE $2"
|
||||||
|
params = {"workspace": self.db.workspace, "prefix": f"{prefix}%"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
results = await self.db.query(search_sql, params, multirows=True)
|
||||||
|
logger.debug(f"Found {len(results)} records with prefix '{prefix}'")
|
||||||
|
|
||||||
|
# Format results to match the expected return format
|
||||||
|
formatted_results = []
|
||||||
|
for record in results:
|
||||||
|
formatted_record = dict(record)
|
||||||
|
# Ensure id field is available (for consistency with NanoVectorDB implementation)
|
||||||
|
if "id" not in formatted_record:
|
||||||
|
formatted_record["id"] = record["id"]
|
||||||
|
formatted_results.append(formatted_record)
|
||||||
|
|
||||||
|
return formatted_results
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during prefix search for '{prefix}': {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -775,42 +810,85 @@ class PGGraphStorage(BaseGraphStorage):
|
|||||||
v = record[k]
|
v = record[k]
|
||||||
# agtype comes back '{key: value}::type' which must be parsed
|
# agtype comes back '{key: value}::type' which must be parsed
|
||||||
if isinstance(v, str) and "::" in v:
|
if isinstance(v, str) and "::" in v:
|
||||||
dtype = v.split("::")[-1]
|
if v.startswith("[") and v.endswith("]"):
|
||||||
v = v.split("::")[0]
|
if "::vertex" not in v:
|
||||||
if dtype == "vertex":
|
continue
|
||||||
vertex = json.loads(v)
|
v = v.replace("::vertex", "")
|
||||||
vertices[vertex["id"]] = vertex.get("properties")
|
vertexes = json.loads(v)
|
||||||
|
for vertex in vertexes:
|
||||||
|
vertices[vertex["id"]] = vertex.get("properties")
|
||||||
|
else:
|
||||||
|
dtype = v.split("::")[-1]
|
||||||
|
v = v.split("::")[0]
|
||||||
|
if dtype == "vertex":
|
||||||
|
vertex = json.loads(v)
|
||||||
|
vertices[vertex["id"]] = vertex.get("properties")
|
||||||
|
|
||||||
# iterate returned fields and parse appropriately
|
# iterate returned fields and parse appropriately
|
||||||
for k in record.keys():
|
for k in record.keys():
|
||||||
v = record[k]
|
v = record[k]
|
||||||
if isinstance(v, str) and "::" in v:
|
if isinstance(v, str) and "::" in v:
|
||||||
dtype = v.split("::")[-1]
|
if v.startswith("[") and v.endswith("]"):
|
||||||
v = v.split("::")[0]
|
if "::vertex" in v:
|
||||||
else:
|
v = v.replace("::vertex", "")
|
||||||
dtype = ""
|
vertexes = json.loads(v)
|
||||||
|
dl = []
|
||||||
|
for vertex in vertexes:
|
||||||
|
prop = vertex.get("properties")
|
||||||
|
if not prop:
|
||||||
|
prop = {}
|
||||||
|
prop["label"] = PGGraphStorage._decode_graph_label(
|
||||||
|
prop["node_id"]
|
||||||
|
)
|
||||||
|
dl.append(prop)
|
||||||
|
d[k] = dl
|
||||||
|
|
||||||
if dtype == "vertex":
|
elif "::edge" in v:
|
||||||
vertex = json.loads(v)
|
v = v.replace("::edge", "")
|
||||||
field = vertex.get("properties")
|
edges = json.loads(v)
|
||||||
if not field:
|
dl = []
|
||||||
field = {}
|
for edge in edges:
|
||||||
field["label"] = PGGraphStorage._decode_graph_label(field["node_id"])
|
dl.append(
|
||||||
d[k] = field
|
(
|
||||||
# convert edge from id-label->id by replacing id with node information
|
vertices[edge["start_id"]],
|
||||||
# we only do this if the vertex was also returned in the query
|
edge["label"],
|
||||||
# this is an attempt to be consistent with neo4j implementation
|
vertices[edge["end_id"]],
|
||||||
elif dtype == "edge":
|
)
|
||||||
edge = json.loads(v)
|
)
|
||||||
d[k] = (
|
d[k] = dl
|
||||||
vertices.get(edge["start_id"], {}),
|
else:
|
||||||
edge[
|
print("WARNING: unsupported type")
|
||||||
"label"
|
continue
|
||||||
], # we don't use decode_graph_label(), since edge label is always "DIRECTED"
|
|
||||||
vertices.get(edge["end_id"], {}),
|
else:
|
||||||
)
|
dtype = v.split("::")[-1]
|
||||||
|
v = v.split("::")[0]
|
||||||
|
if dtype == "vertex":
|
||||||
|
vertex = json.loads(v)
|
||||||
|
field = vertex.get("properties")
|
||||||
|
if not field:
|
||||||
|
field = {}
|
||||||
|
field["label"] = PGGraphStorage._decode_graph_label(
|
||||||
|
field["node_id"]
|
||||||
|
)
|
||||||
|
d[k] = field
|
||||||
|
# convert edge from id-label->id by replacing id with node information
|
||||||
|
# we only do this if the vertex was also returned in the query
|
||||||
|
# this is an attempt to be consistent with neo4j implementation
|
||||||
|
elif dtype == "edge":
|
||||||
|
edge = json.loads(v)
|
||||||
|
d[k] = (
|
||||||
|
vertices.get(edge["start_id"], {}),
|
||||||
|
edge[
|
||||||
|
"label"
|
||||||
|
], # we don't use decode_graph_label(), since edge label is always "DIRECTED"
|
||||||
|
vertices.get(edge["end_id"], {}),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
d[k] = json.loads(v) if isinstance(v, str) else v
|
if v is None or (v.count("{") < 1 and v.count("[") < 1):
|
||||||
|
d[k] = v
|
||||||
|
else:
|
||||||
|
d[k] = json.loads(v) if isinstance(v, str) else v
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@@ -1284,7 +1362,7 @@ class PGGraphStorage(BaseGraphStorage):
|
|||||||
OPTIONAL MATCH p = (n)-[*..%d]-(m)
|
OPTIONAL MATCH p = (n)-[*..%d]-(m)
|
||||||
RETURN nodes(p) AS nodes, relationships(p) AS relationships
|
RETURN nodes(p) AS nodes, relationships(p) AS relationships
|
||||||
LIMIT %d
|
LIMIT %d
|
||||||
$$) AS (nodes agtype[], relationships agtype[])""" % (
|
$$) AS (nodes agtype, relationships agtype)""" % (
|
||||||
self.graph_name,
|
self.graph_name,
|
||||||
encoded_node_label,
|
encoded_node_label,
|
||||||
max_depth,
|
max_depth,
|
||||||
@@ -1293,17 +1371,23 @@ class PGGraphStorage(BaseGraphStorage):
|
|||||||
|
|
||||||
results = await self._query(query)
|
results = await self._query(query)
|
||||||
|
|
||||||
nodes = set()
|
nodes = {}
|
||||||
edges = []
|
edges = []
|
||||||
|
unique_edge_ids = set()
|
||||||
|
|
||||||
for result in results:
|
for result in results:
|
||||||
if node_label == "*":
|
if node_label == "*":
|
||||||
if result["n"]:
|
if result["n"]:
|
||||||
node = result["n"]
|
node = result["n"]
|
||||||
nodes.add(self._decode_graph_label(node["node_id"]))
|
node_id = self._decode_graph_label(node["node_id"])
|
||||||
|
if node_id not in nodes:
|
||||||
|
nodes[node_id] = node
|
||||||
|
|
||||||
if result["m"]:
|
if result["m"]:
|
||||||
node = result["m"]
|
node = result["m"]
|
||||||
nodes.add(self._decode_graph_label(node["node_id"]))
|
node_id = self._decode_graph_label(node["node_id"])
|
||||||
|
if node_id not in nodes:
|
||||||
|
nodes[node_id] = node
|
||||||
if result["r"]:
|
if result["r"]:
|
||||||
edge = result["r"]
|
edge = result["r"]
|
||||||
src_id = self._decode_graph_label(edge["start_id"])
|
src_id = self._decode_graph_label(edge["start_id"])
|
||||||
@@ -1312,16 +1396,36 @@ class PGGraphStorage(BaseGraphStorage):
|
|||||||
else:
|
else:
|
||||||
if result["nodes"]:
|
if result["nodes"]:
|
||||||
for node in result["nodes"]:
|
for node in result["nodes"]:
|
||||||
nodes.add(self._decode_graph_label(node["node_id"]))
|
node_id = self._decode_graph_label(node["node_id"])
|
||||||
|
if node_id not in nodes:
|
||||||
|
nodes[node_id] = node
|
||||||
|
|
||||||
if result["relationships"]:
|
if result["relationships"]:
|
||||||
for edge in result["relationships"]:
|
for edge in result["relationships"]: # src --DIRECTED--> target
|
||||||
src_id = self._decode_graph_label(edge["start_id"])
|
src_id = self._decode_graph_label(edge[0]["node_id"])
|
||||||
tgt_id = self._decode_graph_label(edge["end_id"])
|
tgt_id = self._decode_graph_label(edge[2]["node_id"])
|
||||||
edges.append((src_id, tgt_id))
|
id = src_id + "," + tgt_id
|
||||||
|
if id in unique_edge_ids:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
unique_edge_ids.add(id)
|
||||||
|
edges.append(
|
||||||
|
(id, src_id, tgt_id, {"source": edge[0], "target": edge[2]})
|
||||||
|
)
|
||||||
|
|
||||||
kg = KnowledgeGraph(
|
kg = KnowledgeGraph(
|
||||||
nodes=[KnowledgeGraphNode(id=node_id) for node_id in nodes],
|
nodes=[
|
||||||
edges=[KnowledgeGraphEdge(source=src, target=tgt) for src, tgt in edges],
|
KnowledgeGraphNode(
|
||||||
|
id=node_id, labels=[node_id], properties=nodes[node_id]
|
||||||
|
)
|
||||||
|
for node_id in nodes
|
||||||
|
],
|
||||||
|
edges=[
|
||||||
|
KnowledgeGraphEdge(
|
||||||
|
id=id, type="DIRECTED", source=src, target=tgt, properties=props
|
||||||
|
)
|
||||||
|
for id, src, tgt, props in edges
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return kg
|
return kg
|
||||||
|
@@ -135,7 +135,7 @@ class QdrantVectorDBStorage(BaseVectorStorage):
|
|||||||
|
|
||||||
logger.debug(f"query result: {results}")
|
logger.debug(f"query result: {results}")
|
||||||
|
|
||||||
return [{**dp.payload, "id": dp.id, "distance": dp.score} for dp in results]
|
return [{**dp.payload, "distance": dp.score} for dp in results]
|
||||||
|
|
||||||
async def index_done_callback(self) -> None:
|
async def index_done_callback(self) -> None:
|
||||||
# Qdrant handles persistence automatically
|
# Qdrant handles persistence automatically
|
||||||
@@ -233,3 +233,43 @@ class QdrantVectorDBStorage(BaseVectorStorage):
|
|||||||
logger.debug(f"No relations found for entity {entity_name}")
|
logger.debug(f"No relations found for entity {entity_name}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error deleting relations for {entity_name}: {e}")
|
logger.error(f"Error deleting relations for {entity_name}: {e}")
|
||||||
|
|
||||||
|
async def search_by_prefix(self, prefix: str) -> list[dict[str, Any]]:
|
||||||
|
"""Search for records with IDs starting with a specific prefix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix: The prefix to search for in record IDs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of records with matching ID prefixes
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Use scroll method to find records with IDs starting with the prefix
|
||||||
|
results = self._client.scroll(
|
||||||
|
collection_name=self.namespace,
|
||||||
|
scroll_filter=models.Filter(
|
||||||
|
must=[
|
||||||
|
models.FieldCondition(
|
||||||
|
key="id", match=models.MatchText(text=prefix, prefix=True)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
with_payload=True,
|
||||||
|
with_vectors=False,
|
||||||
|
limit=1000, # Adjust as needed for your use case
|
||||||
|
)
|
||||||
|
|
||||||
|
# Extract matching points
|
||||||
|
matching_records = results[0]
|
||||||
|
|
||||||
|
# Format the results to match expected return format
|
||||||
|
formatted_results = [{**point.payload} for point in matching_records]
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Found {len(formatted_results)} records with prefix '{prefix}'"
|
||||||
|
)
|
||||||
|
return formatted_results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error searching for prefix '{prefix}': {e}")
|
||||||
|
return []
|
||||||
|
@@ -414,6 +414,55 @@ class TiDBVectorDBStorage(BaseVectorStorage):
|
|||||||
# Ti handles persistence automatically
|
# Ti handles persistence automatically
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def search_by_prefix(self, prefix: str) -> list[dict[str, Any]]:
|
||||||
|
"""Search for records with IDs starting with a specific prefix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix: The prefix to search for in record IDs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of records with matching ID prefixes
|
||||||
|
"""
|
||||||
|
# Determine which table to query based on namespace
|
||||||
|
if self.namespace == NameSpace.VECTOR_STORE_ENTITIES:
|
||||||
|
sql_template = """
|
||||||
|
SELECT entity_id as id, name as entity_name, entity_type, description, content
|
||||||
|
FROM LIGHTRAG_GRAPH_NODES
|
||||||
|
WHERE entity_id LIKE :prefix_pattern AND workspace = :workspace
|
||||||
|
"""
|
||||||
|
elif self.namespace == NameSpace.VECTOR_STORE_RELATIONSHIPS:
|
||||||
|
sql_template = """
|
||||||
|
SELECT relation_id as id, source_name as src_id, target_name as tgt_id,
|
||||||
|
keywords, description, content
|
||||||
|
FROM LIGHTRAG_GRAPH_EDGES
|
||||||
|
WHERE relation_id LIKE :prefix_pattern AND workspace = :workspace
|
||||||
|
"""
|
||||||
|
elif self.namespace == NameSpace.VECTOR_STORE_CHUNKS:
|
||||||
|
sql_template = """
|
||||||
|
SELECT chunk_id as id, content, tokens, chunk_order_index, full_doc_id
|
||||||
|
FROM LIGHTRAG_DOC_CHUNKS
|
||||||
|
WHERE chunk_id LIKE :prefix_pattern AND workspace = :workspace
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"Namespace {self.namespace} not supported for prefix search"
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Add prefix pattern parameter with % for SQL LIKE
|
||||||
|
prefix_pattern = f"{prefix}%"
|
||||||
|
params = {"prefix_pattern": prefix_pattern, "workspace": self.db.workspace}
|
||||||
|
|
||||||
|
try:
|
||||||
|
results = await self.db.query(sql_template, params=params, multirows=True)
|
||||||
|
logger.debug(
|
||||||
|
f"Found {len(results) if results else 0} records with prefix '{prefix}'"
|
||||||
|
)
|
||||||
|
return results if results else []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error searching records with prefix '{prefix}': {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -968,4 +1017,20 @@ SQL_TEMPLATES = {
|
|||||||
WHERE (source_name = :source AND target_name = :target)
|
WHERE (source_name = :source AND target_name = :target)
|
||||||
AND workspace = :workspace
|
AND workspace = :workspace
|
||||||
""",
|
""",
|
||||||
|
# Search by prefix SQL templates
|
||||||
|
"search_entity_by_prefix": """
|
||||||
|
SELECT entity_id as id, name as entity_name, entity_type, description, content
|
||||||
|
FROM LIGHTRAG_GRAPH_NODES
|
||||||
|
WHERE entity_id LIKE :prefix_pattern AND workspace = :workspace
|
||||||
|
""",
|
||||||
|
"search_relationship_by_prefix": """
|
||||||
|
SELECT relation_id as id, source_name as src_id, target_name as tgt_id, keywords, description, content
|
||||||
|
FROM LIGHTRAG_GRAPH_EDGES
|
||||||
|
WHERE relation_id LIKE :prefix_pattern AND workspace = :workspace
|
||||||
|
""",
|
||||||
|
"search_chunk_by_prefix": """
|
||||||
|
SELECT chunk_id as id, content, tokens, chunk_order_index, full_doc_id
|
||||||
|
FROM LIGHTRAG_DOC_CHUNKS
|
||||||
|
WHERE chunk_id LIKE :prefix_pattern AND workspace = :workspace
|
||||||
|
""",
|
||||||
}
|
}
|
||||||
|
@@ -1976,6 +1976,9 @@ class LightRAG:
|
|||||||
# Delete old entity record from vector database
|
# Delete old entity record from vector database
|
||||||
old_entity_id = compute_mdhash_id(entity_name, prefix="ent-")
|
old_entity_id = compute_mdhash_id(entity_name, prefix="ent-")
|
||||||
await self.entities_vdb.delete([old_entity_id])
|
await self.entities_vdb.delete([old_entity_id])
|
||||||
|
logger.info(
|
||||||
|
f"Deleted old entity '{entity_name}' and its vector embedding from database"
|
||||||
|
)
|
||||||
|
|
||||||
# Update relationship vector representations
|
# Update relationship vector representations
|
||||||
for src, tgt, edge_data in relations_to_update:
|
for src, tgt, edge_data in relations_to_update:
|
||||||
@@ -2103,6 +2106,15 @@ class LightRAG:
|
|||||||
f"Relation from '{source_entity}' to '{target_entity}' does not exist"
|
f"Relation from '{source_entity}' to '{target_entity}' does not exist"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Important: First delete the old relation record from the vector database
|
||||||
|
old_relation_id = compute_mdhash_id(
|
||||||
|
source_entity + target_entity, prefix="rel-"
|
||||||
|
)
|
||||||
|
await self.relationships_vdb.delete([old_relation_id])
|
||||||
|
logger.info(
|
||||||
|
f"Deleted old relation record from vector database for relation {source_entity} -> {target_entity}"
|
||||||
|
)
|
||||||
|
|
||||||
# 2. Update relation information in the graph
|
# 2. Update relation information in the graph
|
||||||
new_edge_data = {**edge_data, **updated_data}
|
new_edge_data = {**edge_data, **updated_data}
|
||||||
await self.chunk_entity_relation_graph.upsert_edge(
|
await self.chunk_entity_relation_graph.upsert_edge(
|
||||||
@@ -2601,12 +2613,29 @@ class LightRAG:
|
|||||||
|
|
||||||
# 9. Delete source entities
|
# 9. Delete source entities
|
||||||
for entity_name in source_entities:
|
for entity_name in source_entities:
|
||||||
# Delete entity node
|
# Delete entity node from knowledge graph
|
||||||
await self.chunk_entity_relation_graph.delete_node(entity_name)
|
await self.chunk_entity_relation_graph.delete_node(entity_name)
|
||||||
# Delete record from vector database
|
|
||||||
|
# Delete entity record from vector database
|
||||||
entity_id = compute_mdhash_id(entity_name, prefix="ent-")
|
entity_id = compute_mdhash_id(entity_name, prefix="ent-")
|
||||||
await self.entities_vdb.delete([entity_id])
|
await self.entities_vdb.delete([entity_id])
|
||||||
logger.info(f"Deleted source entity '{entity_name}'")
|
|
||||||
|
# Also ensure any relationships specific to this entity are deleted from vector DB
|
||||||
|
# This is a safety check, as these should have been transformed to the target entity already
|
||||||
|
entity_relation_prefix = compute_mdhash_id(entity_name, prefix="rel-")
|
||||||
|
relations_with_entity = await self.relationships_vdb.search_by_prefix(
|
||||||
|
entity_relation_prefix
|
||||||
|
)
|
||||||
|
if relations_with_entity:
|
||||||
|
relation_ids = [r["id"] for r in relations_with_entity]
|
||||||
|
await self.relationships_vdb.delete(relation_ids)
|
||||||
|
logger.info(
|
||||||
|
f"Deleted {len(relation_ids)} relation records for entity '{entity_name}' from vector database"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Deleted source entity '{entity_name}' and its vector embedding from database"
|
||||||
|
)
|
||||||
|
|
||||||
# 10. Save changes
|
# 10. Save changes
|
||||||
await self._merge_entities_done()
|
await self._merge_entities_done()
|
||||||
|
@@ -387,8 +387,8 @@ async def extract_entities(
|
|||||||
language=language,
|
language=language,
|
||||||
)
|
)
|
||||||
|
|
||||||
continue_prompt = PROMPTS["entiti_continue_extraction"]
|
continue_prompt = PROMPTS["entity_continue_extraction"]
|
||||||
if_loop_prompt = PROMPTS["entiti_if_loop_extraction"]
|
if_loop_prompt = PROMPTS["entity_if_loop_extraction"]
|
||||||
|
|
||||||
processed_chunks = 0
|
processed_chunks = 0
|
||||||
total_chunks = len(ordered_chunks)
|
total_chunks = len(ordered_chunks)
|
||||||
@@ -1164,7 +1164,8 @@ async def _get_node_data(
|
|||||||
"entity",
|
"entity",
|
||||||
"type",
|
"type",
|
||||||
"description",
|
"description",
|
||||||
"rank" "created_at",
|
"rank",
|
||||||
|
"created_at",
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
for i, n in enumerate(node_datas):
|
for i, n in enumerate(node_datas):
|
||||||
|
@@ -58,14 +58,16 @@ PROMPTS["entity_extraction_examples"] = [
|
|||||||
|
|
||||||
Entity_types: [person, technology, mission, organization, location]
|
Entity_types: [person, technology, mission, organization, location]
|
||||||
Text:
|
Text:
|
||||||
|
```
|
||||||
while Alex clenched his jaw, the buzz of frustration dull against the backdrop of Taylor's authoritarian certainty. It was this competitive undercurrent that kept him alert, the sense that his and Jordan's shared commitment to discovery was an unspoken rebellion against Cruz's narrowing vision of control and order.
|
while Alex clenched his jaw, the buzz of frustration dull against the backdrop of Taylor's authoritarian certainty. It was this competitive undercurrent that kept him alert, the sense that his and Jordan's shared commitment to discovery was an unspoken rebellion against Cruz's narrowing vision of control and order.
|
||||||
|
|
||||||
Then Taylor did something unexpected. They paused beside Jordan and, for a moment, observed the device with something akin to reverence. "If this tech can be understood..." Taylor said, their voice quieter, "It could change the game for us. For all of us."
|
Then Taylor did something unexpected. They paused beside Jordan and, for a moment, observed the device with something akin to reverence. “If this tech can be understood..." Taylor said, their voice quieter, "It could change the game for us. For all of us.”
|
||||||
|
|
||||||
The underlying dismissal earlier seemed to falter, replaced by a glimpse of reluctant respect for the gravity of what lay in their hands. Jordan looked up, and for a fleeting heartbeat, their eyes locked with Taylor's, a wordless clash of wills softening into an uneasy truce.
|
The underlying dismissal earlier seemed to falter, replaced by a glimpse of reluctant respect for the gravity of what lay in their hands. Jordan looked up, and for a fleeting heartbeat, their eyes locked with Taylor's, a wordless clash of wills softening into an uneasy truce.
|
||||||
|
|
||||||
It was a small transformation, barely perceptible, but one that Alex noted with an inward nod. They had all been brought here by different paths
|
It was a small transformation, barely perceptible, but one that Alex noted with an inward nod. They had all been brought here by different paths
|
||||||
################
|
```
|
||||||
|
|
||||||
Output:
|
Output:
|
||||||
("entity"{tuple_delimiter}"Alex"{tuple_delimiter}"person"{tuple_delimiter}"Alex is a character who experiences frustration and is observant of the dynamics among other characters."){record_delimiter}
|
("entity"{tuple_delimiter}"Alex"{tuple_delimiter}"person"{tuple_delimiter}"Alex is a character who experiences frustration and is observant of the dynamics among other characters."){record_delimiter}
|
||||||
("entity"{tuple_delimiter}"Taylor"{tuple_delimiter}"person"{tuple_delimiter}"Taylor is portrayed with authoritarian certainty and shows a moment of reverence towards a device, indicating a change in perspective."){record_delimiter}
|
("entity"{tuple_delimiter}"Taylor"{tuple_delimiter}"person"{tuple_delimiter}"Taylor is portrayed with authoritarian certainty and shows a moment of reverence towards a device, indicating a change in perspective."){record_delimiter}
|
||||||
@@ -81,48 +83,52 @@ Output:
|
|||||||
#############################""",
|
#############################""",
|
||||||
"""Example 2:
|
"""Example 2:
|
||||||
|
|
||||||
Entity_types: [person, technology, mission, organization, location]
|
Entity_types: [company, index, commodity, market_trend, economic_policy, biological]
|
||||||
Text:
|
Text:
|
||||||
They were no longer mere operatives; they had become guardians of a threshold, keepers of a message from a realm beyond stars and stripes. This elevation in their mission could not be shackled by regulations and established protocols—it demanded a new perspective, a new resolve.
|
```
|
||||||
|
Stock markets faced a sharp downturn today as tech giants saw significant declines, with the Global Tech Index dropping by 3.4% in midday trading. Analysts attribute the selloff to investor concerns over rising interest rates and regulatory uncertainty.
|
||||||
|
|
||||||
Tension threaded through the dialogue of beeps and static as communications with Washington buzzed in the background. The team stood, a portentous air enveloping them. It was clear that the decisions they made in the ensuing hours could redefine humanity's place in the cosmos or condemn them to ignorance and potential peril.
|
Among the hardest hit, Nexon Technologies saw its stock plummet by 7.8% after reporting lower-than-expected quarterly earnings. In contrast, Omega Energy posted a modest 2.1% gain, driven by rising oil prices.
|
||||||
|
|
||||||
|
Meanwhile, commodity markets reflected a mixed sentiment. Gold futures rose by 1.5%, reaching $2,080 per ounce, as investors sought safe-haven assets. Crude oil prices continued their rally, climbing to $87.60 per barrel, supported by supply constraints and strong demand.
|
||||||
|
|
||||||
|
Financial experts are closely watching the Federal Reserve’s next move, as speculation grows over potential rate hikes. The upcoming policy announcement is expected to influence investor confidence and overall market stability.
|
||||||
|
```
|
||||||
|
|
||||||
Their connection to the stars solidified, the group moved to address the crystallizing warning, shifting from passive recipients to active participants. Mercer's latter instincts gained precedence— the team's mandate had evolved, no longer solely to observe and report but to interact and prepare. A metamorphosis had begun, and Operation: Dulce hummed with the newfound frequency of their daring, a tone set not by the earthly
|
|
||||||
#############
|
|
||||||
Output:
|
Output:
|
||||||
("entity"{tuple_delimiter}"Washington"{tuple_delimiter}"location"{tuple_delimiter}"Washington is a location where communications are being received, indicating its importance in the decision-making process."){record_delimiter}
|
("entity"{tuple_delimiter}"Global Tech Index"{tuple_delimiter}"index"{tuple_delimiter}"The Global Tech Index tracks the performance of major technology stocks and experienced a 3.4% decline today."){record_delimiter}
|
||||||
("entity"{tuple_delimiter}"Operation: Dulce"{tuple_delimiter}"mission"{tuple_delimiter}"Operation: Dulce is described as a mission that has evolved to interact and prepare, indicating a significant shift in objectives and activities."){record_delimiter}
|
("entity"{tuple_delimiter}"Nexon Technologies"{tuple_delimiter}"company"{tuple_delimiter}"Nexon Technologies is a tech company that saw its stock decline by 7.8% after disappointing earnings."){record_delimiter}
|
||||||
("entity"{tuple_delimiter}"The team"{tuple_delimiter}"organization"{tuple_delimiter}"The team is portrayed as a group of individuals who have transitioned from passive observers to active participants in a mission, showing a dynamic change in their role."){record_delimiter}
|
("entity"{tuple_delimiter}"Omega Energy"{tuple_delimiter}"company"{tuple_delimiter}"Omega Energy is an energy company that gained 2.1% in stock value due to rising oil prices."){record_delimiter}
|
||||||
("relationship"{tuple_delimiter}"The team"{tuple_delimiter}"Washington"{tuple_delimiter}"The team receives communications from Washington, which influences their decision-making process."{tuple_delimiter}"decision-making, external influence"{tuple_delimiter}7){record_delimiter}
|
("entity"{tuple_delimiter}"Gold Futures"{tuple_delimiter}"commodity"{tuple_delimiter}"Gold futures rose by 1.5%, indicating increased investor interest in safe-haven assets."){record_delimiter}
|
||||||
("relationship"{tuple_delimiter}"The team"{tuple_delimiter}"Operation: Dulce"{tuple_delimiter}"The team is directly involved in Operation: Dulce, executing its evolved objectives and activities."{tuple_delimiter}"mission evolution, active participation"{tuple_delimiter}9){record_delimiter}
|
("entity"{tuple_delimiter}"Crude Oil"{tuple_delimiter}"commodity"{tuple_delimiter}"Crude oil prices rose to $87.60 per barrel due to supply constraints and strong demand."){record_delimiter}
|
||||||
("content_keywords"{tuple_delimiter}"mission evolution, decision-making, active participation, cosmic significance"){completion_delimiter}
|
("entity"{tuple_delimiter}"Market Selloff"{tuple_delimiter}"market_trend"{tuple_delimiter}"Market selloff refers to the significant decline in stock values due to investor concerns over interest rates and regulations."){record_delimiter}
|
||||||
|
("entity"{tuple_delimiter}"Federal Reserve Policy Announcement"{tuple_delimiter}"economic_policy"{tuple_delimiter}"The Federal Reserve's upcoming policy announcement is expected to impact investor confidence and market stability."){record_delimiter}
|
||||||
|
("relationship"{tuple_delimiter}"Global Tech Index"{tuple_delimiter}"Market Selloff"{tuple_delimiter}"The decline in the Global Tech Index is part of the broader market selloff driven by investor concerns."{tuple_delimiter}"market performance, investor sentiment"{tuple_delimiter}9){record_delimiter}
|
||||||
|
("relationship"{tuple_delimiter}"Nexon Technologies"{tuple_delimiter}"Global Tech Index"{tuple_delimiter}"Nexon Technologies' stock decline contributed to the overall drop in the Global Tech Index."{tuple_delimiter}"company impact, index movement"{tuple_delimiter}8){record_delimiter}
|
||||||
|
("relationship"{tuple_delimiter}"Gold Futures"{tuple_delimiter}"Market Selloff"{tuple_delimiter}"Gold prices rose as investors sought safe-haven assets during the market selloff."{tuple_delimiter}"market reaction, safe-haven investment"{tuple_delimiter}10){record_delimiter}
|
||||||
|
("relationship"{tuple_delimiter}"Federal Reserve Policy Announcement"{tuple_delimiter}"Market Selloff"{tuple_delimiter}"Speculation over Federal Reserve policy changes contributed to market volatility and investor selloff."{tuple_delimiter}"interest rate impact, financial regulation"{tuple_delimiter}7){record_delimiter}
|
||||||
|
("content_keywords"{tuple_delimiter}"market downturn, investor sentiment, commodities, Federal Reserve, stock performance"){completion_delimiter}
|
||||||
#############################""",
|
#############################""",
|
||||||
"""Example 3:
|
"""Example 3:
|
||||||
|
|
||||||
Entity_types: [person, role, technology, organization, event, location, concept]
|
Entity_types: [economic_policy, athlete, event, location, record, organization, equipment]
|
||||||
Text:
|
Text:
|
||||||
their voice slicing through the buzz of activity. "Control may be an illusion when facing an intelligence that literally writes its own rules," they stated stoically, casting a watchful eye over the flurry of data.
|
```
|
||||||
|
At the World Athletics Championship in Tokyo, Noah Carter broke the 100m sprint record using cutting-edge carbon-fiber spikes.
|
||||||
|
```
|
||||||
|
|
||||||
"It's like it's learning to communicate," offered Sam Rivera from a nearby interface, their youthful energy boding a mix of awe and anxiety. "This gives talking to strangers' a whole new meaning."
|
|
||||||
|
|
||||||
Alex surveyed his team—each face a study in concentration, determination, and not a small measure of trepidation. "This might well be our first contact," he acknowledged, "And we need to be ready for whatever answers back."
|
|
||||||
|
|
||||||
Together, they stood on the edge of the unknown, forging humanity's response to a message from the heavens. The ensuing silence was palpable—a collective introspection about their role in this grand cosmic play, one that could rewrite human history.
|
|
||||||
|
|
||||||
The encrypted dialogue continued to unfold, its intricate patterns showing an almost uncanny anticipation
|
|
||||||
#############
|
|
||||||
Output:
|
Output:
|
||||||
("entity"{tuple_delimiter}"Sam Rivera"{tuple_delimiter}"person"{tuple_delimiter}"Sam Rivera is a member of a team working on communicating with an unknown intelligence, showing a mix of awe and anxiety."){record_delimiter}
|
("entity"{tuple_delimiter}"World Athletics Championship"{tuple_delimiter}"event"{tuple_delimiter}"The World Athletics Championship is a global sports competition featuring top athletes in track and field."){record_delimiter}
|
||||||
("entity"{tuple_delimiter}"Alex"{tuple_delimiter}"person"{tuple_delimiter}"Alex is the leader of a team attempting first contact with an unknown intelligence, acknowledging the significance of their task."){record_delimiter}
|
("entity"{tuple_delimiter}"Tokyo"{tuple_delimiter}"location"{tuple_delimiter}"Tokyo is the host city of the World Athletics Championship."){record_delimiter}
|
||||||
("entity"{tuple_delimiter}"Control"{tuple_delimiter}"concept"{tuple_delimiter}"Control refers to the ability to manage or govern, which is challenged by an intelligence that writes its own rules."){record_delimiter}
|
("entity"{tuple_delimiter}"Noah Carter"{tuple_delimiter}"athlete"{tuple_delimiter}"Noah Carter is a sprinter who set a new record in the 100m sprint at the World Athletics Championship."){record_delimiter}
|
||||||
("entity"{tuple_delimiter}"Intelligence"{tuple_delimiter}"concept"{tuple_delimiter}"Intelligence here refers to an unknown entity capable of writing its own rules and learning to communicate."){record_delimiter}
|
("entity"{tuple_delimiter}"100m Sprint Record"{tuple_delimiter}"record"{tuple_delimiter}"The 100m sprint record is a benchmark in athletics, recently broken by Noah Carter."){record_delimiter}
|
||||||
("entity"{tuple_delimiter}"First Contact"{tuple_delimiter}"event"{tuple_delimiter}"First Contact is the potential initial communication between humanity and an unknown intelligence."){record_delimiter}
|
("entity"{tuple_delimiter}"Carbon-Fiber Spikes"{tuple_delimiter}"equipment"{tuple_delimiter}"Carbon-fiber spikes are advanced sprinting shoes that provide enhanced speed and traction."){record_delimiter}
|
||||||
("entity"{tuple_delimiter}"Humanity's Response"{tuple_delimiter}"event"{tuple_delimiter}"Humanity's Response is the collective action taken by Alex's team in response to a message from an unknown intelligence."){record_delimiter}
|
("entity"{tuple_delimiter}"World Athletics Federation"{tuple_delimiter}"organization"{tuple_delimiter}"The World Athletics Federation is the governing body overseeing the World Athletics Championship and record validations."){record_delimiter}
|
||||||
("relationship"{tuple_delimiter}"Sam Rivera"{tuple_delimiter}"Intelligence"{tuple_delimiter}"Sam Rivera is directly involved in the process of learning to communicate with the unknown intelligence."{tuple_delimiter}"communication, learning process"{tuple_delimiter}9){record_delimiter}
|
("relationship"{tuple_delimiter}"World Athletics Championship"{tuple_delimiter}"Tokyo"{tuple_delimiter}"The World Athletics Championship is being hosted in Tokyo."{tuple_delimiter}"event location, international competition"{tuple_delimiter}8){record_delimiter}
|
||||||
("relationship"{tuple_delimiter}"Alex"{tuple_delimiter}"First Contact"{tuple_delimiter}"Alex leads the team that might be making the First Contact with the unknown intelligence."{tuple_delimiter}"leadership, exploration"{tuple_delimiter}10){record_delimiter}
|
("relationship"{tuple_delimiter}"Noah Carter"{tuple_delimiter}"100m Sprint Record"{tuple_delimiter}"Noah Carter set a new 100m sprint record at the championship."{tuple_delimiter}"athlete achievement, record-breaking"{tuple_delimiter}10){record_delimiter}
|
||||||
("relationship"{tuple_delimiter}"Alex"{tuple_delimiter}"Humanity's Response"{tuple_delimiter}"Alex and his team are the key figures in Humanity's Response to the unknown intelligence."{tuple_delimiter}"collective action, cosmic significance"{tuple_delimiter}8){record_delimiter}
|
("relationship"{tuple_delimiter}"Noah Carter"{tuple_delimiter}"Carbon-Fiber Spikes"{tuple_delimiter}"Noah Carter used carbon-fiber spikes to enhance performance during the race."{tuple_delimiter}"athletic equipment, performance boost"{tuple_delimiter}7){record_delimiter}
|
||||||
("relationship"{tuple_delimiter}"Control"{tuple_delimiter}"Intelligence"{tuple_delimiter}"The concept of Control is challenged by the Intelligence that writes its own rules."{tuple_delimiter}"power dynamics, autonomy"{tuple_delimiter}7){record_delimiter}
|
("relationship"{tuple_delimiter}"World Athletics Federation"{tuple_delimiter}"100m Sprint Record"{tuple_delimiter}"The World Athletics Federation is responsible for validating and recognizing new sprint records."{tuple_delimiter}"sports regulation, record certification"{tuple_delimiter}9){record_delimiter}
|
||||||
("content_keywords"{tuple_delimiter}"first contact, control, communication, cosmic significance"){completion_delimiter}
|
("content_keywords"{tuple_delimiter}"athletics, sprinting, record-breaking, sports technology, competition"){completion_delimiter}
|
||||||
#############################""",
|
#############################""",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -143,15 +149,47 @@ Description List: {description_list}
|
|||||||
Output:
|
Output:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PROMPTS[
|
PROMPTS["entity_continue_extraction"] = """
|
||||||
"entiti_continue_extraction"
|
MANY entities and relationships were missed in the last extraction.
|
||||||
] = """MANY entities were missed in the last extraction. Add them below using the same format:
|
|
||||||
"""
|
|
||||||
|
|
||||||
PROMPTS[
|
---Remember Steps---
|
||||||
"entiti_if_loop_extraction"
|
|
||||||
] = """It appears some entities may have still been missed. Answer YES | NO if there are still entities that need to be added.
|
1. Identify all entities. For each identified entity, extract the following information:
|
||||||
"""
|
- entity_name: Name of the entity, use same language as input text. If English, capitalized the name.
|
||||||
|
- entity_type: One of the following types: [{entity_types}]
|
||||||
|
- entity_description: Comprehensive description of the entity's attributes and activities
|
||||||
|
Format each entity as ("entity"{tuple_delimiter}<entity_name>{tuple_delimiter}<entity_type>{tuple_delimiter}<entity_description>
|
||||||
|
|
||||||
|
2. From the entities identified in step 1, identify all pairs of (source_entity, target_entity) that are *clearly related* to each other.
|
||||||
|
For each pair of related entities, extract the following information:
|
||||||
|
- source_entity: name of the source entity, as identified in step 1
|
||||||
|
- target_entity: name of the target entity, as identified in step 1
|
||||||
|
- relationship_description: explanation as to why you think the source entity and the target entity are related to each other
|
||||||
|
- relationship_strength: a numeric score indicating strength of the relationship between the source entity and target entity
|
||||||
|
- relationship_keywords: one or more high-level key words that summarize the overarching nature of the relationship, focusing on concepts or themes rather than specific details
|
||||||
|
Format each relationship as ("relationship"{tuple_delimiter}<source_entity>{tuple_delimiter}<target_entity>{tuple_delimiter}<relationship_description>{tuple_delimiter}<relationship_keywords>{tuple_delimiter}<relationship_strength>)
|
||||||
|
|
||||||
|
3. Identify high-level key words that summarize the main concepts, themes, or topics of the entire text. These should capture the overarching ideas present in the document.
|
||||||
|
Format the content-level key words as ("content_keywords"{tuple_delimiter}<high_level_keywords>)
|
||||||
|
|
||||||
|
4. Return output in {language} as a single list of all the entities and relationships identified in steps 1 and 2. Use **{record_delimiter}** as the list delimiter.
|
||||||
|
|
||||||
|
5. When finished, output {completion_delimiter}
|
||||||
|
|
||||||
|
---Output---
|
||||||
|
|
||||||
|
Add them below using the same format:\n
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
PROMPTS["entity_if_loop_extraction"] = """
|
||||||
|
---Goal---'
|
||||||
|
|
||||||
|
It appears some entities may have still been missed.
|
||||||
|
|
||||||
|
---Output---
|
||||||
|
|
||||||
|
Answer ONLY by `YES` OR `NO` if there are still entities that need to be added.
|
||||||
|
""".strip()
|
||||||
|
|
||||||
PROMPTS["fail_response"] = (
|
PROMPTS["fail_response"] = (
|
||||||
"Sorry, I'm not able to provide an answer to that question.[no-context]"
|
"Sorry, I'm not able to provide an answer to that question.[no-context]"
|
||||||
|
Reference in New Issue
Block a user