graphrag_sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- graphrag_sdk-0.1.0/PKG-INFO +155 -0
- graphrag_sdk-0.1.0/README.md +133 -0
- graphrag_sdk-0.1.0/graphrag_sdk/__init__.py +2 -0
- graphrag_sdk-0.1.0/graphrag_sdk/kg.py +155 -0
- graphrag_sdk-0.1.0/graphrag_sdk/prompts.py +76 -0
- graphrag_sdk-0.1.0/graphrag_sdk/query_graph.py +144 -0
- graphrag_sdk-0.1.0/graphrag_sdk/schema/__init__.py +1 -0
- graphrag_sdk-0.1.0/graphrag_sdk/schema/auto_detect.py +456 -0
- graphrag_sdk-0.1.0/graphrag_sdk/schema/decoder.py +80 -0
- graphrag_sdk-0.1.0/graphrag_sdk/schema/encoder.py +45 -0
- graphrag_sdk-0.1.0/graphrag_sdk/schema/entity.py +129 -0
- graphrag_sdk-0.1.0/graphrag_sdk/schema/from_graph.py +48 -0
- graphrag_sdk-0.1.0/graphrag_sdk/schema/functions_gen.py +74 -0
- graphrag_sdk-0.1.0/graphrag_sdk/schema/relation.py +28 -0
- graphrag_sdk-0.1.0/graphrag_sdk/schema/schema.py +216 -0
- graphrag_sdk-0.1.0/graphrag_sdk/schema/to_graph.py +54 -0
- graphrag_sdk-0.1.0/graphrag_sdk/schema/tools_gen.py +138 -0
- graphrag_sdk-0.1.0/graphrag_sdk/source.py +97 -0
- graphrag_sdk-0.1.0/graphrag_sdk/template.py +153 -0
- graphrag_sdk-0.1.0/pyproject.toml +29 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: graphrag_sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Home-page: http://RAG-SDK.readthedocs.io
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: RAG,Graph-RAG,KnowledgeGraph
|
|
8
|
+
Author: FalkorDB
|
|
9
|
+
Author-email: info@falkordb.com
|
|
10
|
+
Requires-Python: >=3.12,<4.0
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: bs4 (>=0.0.2,<0.0.3)
|
|
15
|
+
Requires-Dist: falkordb (>=1.0.4,<2.0.0)
|
|
16
|
+
Requires-Dist: openai (>=1.30.3,<2.0.0)
|
|
17
|
+
Requires-Dist: sqlalchemy (>=2.0.30,<3.0.0)
|
|
18
|
+
Requires-Dist: typing-extensions (>=4.12.1,<5.0.0)
|
|
19
|
+
Project-URL: Repository, https://github.com/FalkorDB/GraphRAG-SDK
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# GraphRAG-SDK
|
|
23
|
+
|
|
24
|
+
## Example
|
|
25
|
+
|
|
26
|
+
### Prerequisites
|
|
27
|
+
GraphRAG-SDK relies on [FalkorDB](http://falkordb.com) as its graph engine and works with OpenAI.
|
|
28
|
+
|
|
29
|
+
Start FalkorDB locally:
|
|
30
|
+
```sh
|
|
31
|
+
docker run -p 6379:6379 -it --rm -v ./data:/data falkordb/falkordb:edge
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Export your OpenAI API KEY:
|
|
35
|
+
```sh
|
|
36
|
+
export OPENAI_API_KEY=<YOUR_OPENAI_KEY>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from graphrag_sdk.schema import Schema
|
|
41
|
+
from graphrag_sdk import KnowledgeGraph, Source
|
|
42
|
+
|
|
43
|
+
# Auto generate graph schema from unstructured data
|
|
44
|
+
sources = [Source("./data/the_matrix.txt")]
|
|
45
|
+
s = Schema.auto_detect(sources)
|
|
46
|
+
|
|
47
|
+
# Create a knowledge graph based on schema
|
|
48
|
+
g = KnowledgeGraph("IMDB", schema=s)
|
|
49
|
+
g.process_sources(sources)
|
|
50
|
+
|
|
51
|
+
# Query your data
|
|
52
|
+
question = "Name a few actors who've played in 'The Matrix'"
|
|
53
|
+
answer, messages = g.ask(question)
|
|
54
|
+
print(f"Answer: {answer}")
|
|
55
|
+
|
|
56
|
+
# Output:
|
|
57
|
+
# Answer: A few actors who've played in 'The Matrix' are:
|
|
58
|
+
# - Keanu Reeves
|
|
59
|
+
# - Laurence Fishburne
|
|
60
|
+
# - Carrie-Anne Moss
|
|
61
|
+
# - Hugo Weaving
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Introduction
|
|
65
|
+
GraphRAG-SDK provides easy-to-use tooling to get you up and running with your own
|
|
66
|
+
Graph-RAG solution.
|
|
67
|
+
|
|
68
|
+
There are two main components:
|
|
69
|
+
### Schema
|
|
70
|
+
A `schema` represents the types of entities and relationships within your data.
|
|
71
|
+
For example, the main entities in your data are: Movies, Actors, and Directors.
|
|
72
|
+
These are interconnected via `ACT` and `DIRECTED` edges.
|
|
73
|
+
|
|
74
|
+
Two approaches to schema creation are available:
|
|
75
|
+
|
|
76
|
+
#### Manual schema creation
|
|
77
|
+
Use this method when you know exactly how your data should be structured.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
s = Schema()
|
|
81
|
+
s.add_entity('Actor').add_attribute('name', str, unique=True)
|
|
82
|
+
s.add_entity('Movie').add_attribute('title', str, unique=True)
|
|
83
|
+
s.add_relation("ACTED", 'Actor', 'Movie')
|
|
84
|
+
|
|
85
|
+
print(f"Schema: {s.to_JSON()}")
|
|
86
|
+
|
|
87
|
+
# Output:
|
|
88
|
+
# Schema: {"entities": [
|
|
89
|
+
# {"name": "Actor",
|
|
90
|
+
# "attributes": [
|
|
91
|
+
# {"name": "name", "type": "str", "desc": "Actor's name", "unique": true, "mandatory": false}]},
|
|
92
|
+
# {"name": "Movie",
|
|
93
|
+
# "attributes": [
|
|
94
|
+
# {"name": "title", "type": "str", "desc": "Movie's title", "unique": true, "mandatory": false}]}],
|
|
95
|
+
# "relations": [{"name": "ACTED", "src": "Actor", "dest": "Movie"}]}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Automatic schema creation
|
|
99
|
+
Use this method to discover the main entities and relationships within your data.
|
|
100
|
+
Once the schema is discovered, you can adjust it to your liking.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
sources = [Source("./data/madoff.txt")]
|
|
104
|
+
s = Schema.auto_detect(sources)
|
|
105
|
+
json_schema = s.to_JSON()
|
|
106
|
+
print(f"Schema: {json_schema}")
|
|
107
|
+
|
|
108
|
+
# Adjust json to your liking and reload the schema from JSON
|
|
109
|
+
json_schema['entities'].append({"name": "Movie", "attributes": [ {"name": "Title", "type": "str", "desc": "Movie's title", "unique": true, "mandatory": true}])
|
|
110
|
+
|
|
111
|
+
# Recreate refined schema
|
|
112
|
+
s = Schema.from_JSON(json_schema)
|
|
113
|
+
|
|
114
|
+
# Output:
|
|
115
|
+
# Schema: {"entities": [
|
|
116
|
+
# {"name": "Actor", "attributes": [
|
|
117
|
+
# {"name": "Name", "type": "str", "desc": "Actor's Name", "unique": true, "mandatory": true}]},
|
|
118
|
+
# {"name": "Critic", "attributes": [
|
|
119
|
+
# {"name": "TopCritic", "type": "bool", "desc": "Critic's TopCritic", "unique": false, "mandatory": false},
|
|
120
|
+
# {"name": "Name", "type": "str", "desc": "Critic's Name", "unique": true, "mandatory": true},
|
|
121
|
+
# {"name": "Publication", "type": "str", "desc": "Critic's Publication", "unique": false, "mandatory": false}]}...
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### KnowledgeGraph
|
|
125
|
+
A `KnowledgeGraph` holds the actual entities and relationships within your data.
|
|
126
|
+
Once constructed, it serves as the backbone of your RAG solution. A Large Language Model will query your knowledge-graph to build a precise context on which its answer will be based.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# Create a knowledge graph based on a schema.
|
|
130
|
+
g = KnowledgeGraph("IMDB", host="127.0.0.1", port=6379, schema=s)
|
|
131
|
+
g.process_sources([Source("./data/the_matrix.txt"), Source("./data/wework.txt")])
|
|
132
|
+
|
|
133
|
+
# Query your data
|
|
134
|
+
question = "Name a few actors who've played in 'The Matrix'"
|
|
135
|
+
answer, messages = g.ask(question)
|
|
136
|
+
print(f"Answer: {answer}")
|
|
137
|
+
|
|
138
|
+
question = "List additional actors"
|
|
139
|
+
answer, messages = g.ask(question, messages)
|
|
140
|
+
print(f"Answer: {answer}")
|
|
141
|
+
|
|
142
|
+
# Outputs:
|
|
143
|
+
# Answer: A few actors who've played in 'The Matrix' are:
|
|
144
|
+
# - Keanu Reeves
|
|
145
|
+
# - Laurence Fishburne
|
|
146
|
+
# - Carrie-Anne Moss
|
|
147
|
+
# - Hugo Weaving
|
|
148
|
+
#
|
|
149
|
+
# Answer: A few additional actors who've played in 'The Matrix' are:
|
|
150
|
+
# - Joe Pantoliano
|
|
151
|
+
# - Marcus Chong
|
|
152
|
+
# - Paul Goddard
|
|
153
|
+
# - Gloria Foster
|
|
154
|
+
```
|
|
155
|
+
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# GraphRAG-SDK
|
|
2
|
+
|
|
3
|
+
## Example
|
|
4
|
+
|
|
5
|
+
### Prerequisites
|
|
6
|
+
GraphRAG-SDK relies on [FalkorDB](http://falkordb.com) as its graph engine and works with OpenAI.
|
|
7
|
+
|
|
8
|
+
Start FalkorDB locally:
|
|
9
|
+
```sh
|
|
10
|
+
docker run -p 6379:6379 -it --rm -v ./data:/data falkordb/falkordb:edge
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Export your OpenAI API KEY:
|
|
14
|
+
```sh
|
|
15
|
+
export OPENAI_API_KEY=<YOUR_OPENAI_KEY>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from graphrag_sdk.schema import Schema
|
|
20
|
+
from graphrag_sdk import KnowledgeGraph, Source
|
|
21
|
+
|
|
22
|
+
# Auto generate graph schema from unstructured data
|
|
23
|
+
sources = [Source("./data/the_matrix.txt")]
|
|
24
|
+
s = Schema.auto_detect(sources)
|
|
25
|
+
|
|
26
|
+
# Create a knowledge graph based on schema
|
|
27
|
+
g = KnowledgeGraph("IMDB", schema=s)
|
|
28
|
+
g.process_sources(sources)
|
|
29
|
+
|
|
30
|
+
# Query your data
|
|
31
|
+
question = "Name a few actors who've played in 'The Matrix'"
|
|
32
|
+
answer, messages = g.ask(question)
|
|
33
|
+
print(f"Answer: {answer}")
|
|
34
|
+
|
|
35
|
+
# Output:
|
|
36
|
+
# Answer: A few actors who've played in 'The Matrix' are:
|
|
37
|
+
# - Keanu Reeves
|
|
38
|
+
# - Laurence Fishburne
|
|
39
|
+
# - Carrie-Anne Moss
|
|
40
|
+
# - Hugo Weaving
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Introduction
|
|
44
|
+
GraphRAG-SDK provides easy-to-use tooling to get you up and running with your own
|
|
45
|
+
Graph-RAG solution.
|
|
46
|
+
|
|
47
|
+
There are two main components:
|
|
48
|
+
### Schema
|
|
49
|
+
A `schema` represents the types of entities and relationships within your data.
|
|
50
|
+
For example, the main entities in your data are: Movies, Actors, and Directors.
|
|
51
|
+
These are interconnected via `ACT` and `DIRECTED` edges.
|
|
52
|
+
|
|
53
|
+
Two approaches to schema creation are available:
|
|
54
|
+
|
|
55
|
+
#### Manual schema creation
|
|
56
|
+
Use this method when you know exactly how your data should be structured.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
s = Schema()
|
|
60
|
+
s.add_entity('Actor').add_attribute('name', str, unique=True)
|
|
61
|
+
s.add_entity('Movie').add_attribute('title', str, unique=True)
|
|
62
|
+
s.add_relation("ACTED", 'Actor', 'Movie')
|
|
63
|
+
|
|
64
|
+
print(f"Schema: {s.to_JSON()}")
|
|
65
|
+
|
|
66
|
+
# Output:
|
|
67
|
+
# Schema: {"entities": [
|
|
68
|
+
# {"name": "Actor",
|
|
69
|
+
# "attributes": [
|
|
70
|
+
# {"name": "name", "type": "str", "desc": "Actor's name", "unique": true, "mandatory": false}]},
|
|
71
|
+
# {"name": "Movie",
|
|
72
|
+
# "attributes": [
|
|
73
|
+
# {"name": "title", "type": "str", "desc": "Movie's title", "unique": true, "mandatory": false}]}],
|
|
74
|
+
# "relations": [{"name": "ACTED", "src": "Actor", "dest": "Movie"}]}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Automatic schema creation
|
|
78
|
+
Use this method to discover the main entities and relationships within your data.
|
|
79
|
+
Once the schema is discovered, you can adjust it to your liking.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
sources = [Source("./data/madoff.txt")]
|
|
83
|
+
s = Schema.auto_detect(sources)
|
|
84
|
+
json_schema = s.to_JSON()
|
|
85
|
+
print(f"Schema: {json_schema}")
|
|
86
|
+
|
|
87
|
+
# Adjust json to your liking and reload the schema from JSON
|
|
88
|
+
json_schema['entities'].append({"name": "Movie", "attributes": [ {"name": "Title", "type": "str", "desc": "Movie's title", "unique": true, "mandatory": true}])
|
|
89
|
+
|
|
90
|
+
# Recreate refined schema
|
|
91
|
+
s = Schema.from_JSON(json_schema)
|
|
92
|
+
|
|
93
|
+
# Output:
|
|
94
|
+
# Schema: {"entities": [
|
|
95
|
+
# {"name": "Actor", "attributes": [
|
|
96
|
+
# {"name": "Name", "type": "str", "desc": "Actor's Name", "unique": true, "mandatory": true}]},
|
|
97
|
+
# {"name": "Critic", "attributes": [
|
|
98
|
+
# {"name": "TopCritic", "type": "bool", "desc": "Critic's TopCritic", "unique": false, "mandatory": false},
|
|
99
|
+
# {"name": "Name", "type": "str", "desc": "Critic's Name", "unique": true, "mandatory": true},
|
|
100
|
+
# {"name": "Publication", "type": "str", "desc": "Critic's Publication", "unique": false, "mandatory": false}]}...
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### KnowledgeGraph
|
|
104
|
+
A `KnowledgeGraph` holds the actual entities and relationships within your data.
|
|
105
|
+
Once constructed, it serves as the backbone of your RAG solution. A Large Language Model will query your knowledge-graph to build a precise context on which its answer will be based.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
# Create a knowledge graph based on a schema.
|
|
109
|
+
g = KnowledgeGraph("IMDB", host="127.0.0.1", port=6379, schema=s)
|
|
110
|
+
g.process_sources([Source("./data/the_matrix.txt"), Source("./data/wework.txt")])
|
|
111
|
+
|
|
112
|
+
# Query your data
|
|
113
|
+
question = "Name a few actors who've played in 'The Matrix'"
|
|
114
|
+
answer, messages = g.ask(question)
|
|
115
|
+
print(f"Answer: {answer}")
|
|
116
|
+
|
|
117
|
+
question = "List additional actors"
|
|
118
|
+
answer, messages = g.ask(question, messages)
|
|
119
|
+
print(f"Answer: {answer}")
|
|
120
|
+
|
|
121
|
+
# Outputs:
|
|
122
|
+
# Answer: A few actors who've played in 'The Matrix' are:
|
|
123
|
+
# - Keanu Reeves
|
|
124
|
+
# - Laurence Fishburne
|
|
125
|
+
# - Carrie-Anne Moss
|
|
126
|
+
# - Hugo Weaving
|
|
127
|
+
#
|
|
128
|
+
# Answer: A few additional actors who've played in 'The Matrix' are:
|
|
129
|
+
# - Joe Pantoliano
|
|
130
|
+
# - Marcus Chong
|
|
131
|
+
# - Paul Goddard
|
|
132
|
+
# - Gloria Foster
|
|
133
|
+
```
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import queue
|
|
3
|
+
import concurrent.futures
|
|
4
|
+
from typing_extensions import AbstractSet
|
|
5
|
+
|
|
6
|
+
from .prompts import *
|
|
7
|
+
from .template import TEMPLATE
|
|
8
|
+
from openai import OpenAI
|
|
9
|
+
from falkordb import FalkorDB
|
|
10
|
+
from .query_graph import query
|
|
11
|
+
from .schema.schema import Schema
|
|
12
|
+
from .source import Source, AbstractSource
|
|
13
|
+
from .schema.tools_gen import schema_to_tools
|
|
14
|
+
from .schema.functions_gen import schema_to_functions
|
|
15
|
+
|
|
16
|
+
class KnowledgeGraph(object):
|
|
17
|
+
"""Knowledge Graph model data as a network of entities and relations
|
|
18
|
+
To create one it is best to provide a schema which will define the graph's ontology
|
|
19
|
+
In addition to a set of sources from which entities and relations will be extracted.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, name:str, host:str="127.0.0.1", port:int=6379, username:str|None=None, password:str|None=None,
|
|
23
|
+
model:str="gpt-4-1106-preview", schema:Schema|None=None):
|
|
24
|
+
"""
|
|
25
|
+
Initialize Knowledge Graph
|
|
26
|
+
|
|
27
|
+
Parameters:
|
|
28
|
+
name (str): Knowledge graph name.
|
|
29
|
+
host (str): FalkorDB hostname.
|
|
30
|
+
port (int): FalkorDB port number.
|
|
31
|
+
username (str|None): FalkorDB username.
|
|
32
|
+
password (str|None): FalkorDB password.
|
|
33
|
+
model (str): OpenAI model to use.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
if not isinstance(name, str) or name == "":
|
|
37
|
+
raise Exception("name should be a non empty string")
|
|
38
|
+
|
|
39
|
+
# connect to database
|
|
40
|
+
self.db = FalkorDB(host=host, port=port, username=username, password=password)
|
|
41
|
+
self.graph = self.db.select_graph(name)
|
|
42
|
+
|
|
43
|
+
self.name = name
|
|
44
|
+
self.model = model
|
|
45
|
+
self.schema = schema
|
|
46
|
+
self.client = None # OpenAI client
|
|
47
|
+
self.sources = set([])
|
|
48
|
+
self.ontolegy_ratio = 0.1 # Sampaling ratio for ontolegy detection
|
|
49
|
+
|
|
50
|
+
# in case schema is None
|
|
51
|
+
# try to load schema from FalkorDB
|
|
52
|
+
if schema is None:
|
|
53
|
+
schema_name = self._schema_name()
|
|
54
|
+
if schema_name in self.db.list_graphs():
|
|
55
|
+
schema_graph = self.db.select_graph()
|
|
56
|
+
self.schema = self.schema.from_graph(schema_graph)
|
|
57
|
+
|
|
58
|
+
def list_sources(self) -> list[AbstractSource]:
|
|
59
|
+
"""
|
|
60
|
+
List of sources associated with knowledge graph
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
list[AbstractSource]: sources
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
return [s.source for s in self.sources]
|
|
67
|
+
|
|
68
|
+
def process_sources(self, sources: list[AbstractSource]) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Add entities and relations found in sources into the knowledge-graph
|
|
71
|
+
|
|
72
|
+
Parameters:
|
|
73
|
+
sources (list[AbstractSource]): list of sources to extract knowledge from
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
# Make sure knowledge graph is created.
|
|
77
|
+
if not 'build_graph_from_sources' in globals() or self.client is None:
|
|
78
|
+
self._create()
|
|
79
|
+
|
|
80
|
+
# Run assistant
|
|
81
|
+
result = build_graph_from_sources(self, self.client, sources)
|
|
82
|
+
|
|
83
|
+
# Add processed sources
|
|
84
|
+
for src in sources:
|
|
85
|
+
self.sources.add(src)
|
|
86
|
+
|
|
87
|
+
def _schema_name(self) -> str:
|
|
88
|
+
"""
|
|
89
|
+
Generate a name for the schema based on the Knowledge Graph name
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
return f"{self.name}_schema"
|
|
93
|
+
|
|
94
|
+
def _createKGWithSchema(self):
|
|
95
|
+
# Save schema as an ontology graph
|
|
96
|
+
schema_graph = self.db.select_graph(self._schema_name())
|
|
97
|
+
self.schema.save_graph(schema_graph)
|
|
98
|
+
|
|
99
|
+
# Create Knowledge Graph construction assistant
|
|
100
|
+
|
|
101
|
+
# Generate assistant's toolings
|
|
102
|
+
tools = schema_to_tools(self.schema)
|
|
103
|
+
|
|
104
|
+
# Generate functions from schema
|
|
105
|
+
funcs = schema_to_functions(self.schema)
|
|
106
|
+
functions_names = [func[0] for func in funcs]
|
|
107
|
+
functions_impl = [func[1] for func in funcs]
|
|
108
|
+
|
|
109
|
+
# Generate assistant
|
|
110
|
+
# Generate code from template
|
|
111
|
+
code = TEMPLATE
|
|
112
|
+
code = code.replace("<TOOLS>", ",\n".join([str(tool) for tool in tools]))
|
|
113
|
+
code = code.replace("<GRAPH_ID>", "\"" + self.name + "\"")
|
|
114
|
+
code = code.replace("<FUNCTIONS>", "\n".join(functions_impl))
|
|
115
|
+
code = code.replace("<AVAILABLE_FUNCTIONS>", '{' + ",\n".join([f"'{f}': {f}" for f in functions_names]) +'}')
|
|
116
|
+
|
|
117
|
+
# Execute the code string in the global namespace
|
|
118
|
+
# Introduces the 'build_graph_from_sources' function
|
|
119
|
+
# TODO: note multiple calls to _createKGWithSchema will overide one another.
|
|
120
|
+
exec(code, globals())
|
|
121
|
+
|
|
122
|
+
def _createKGWithoutSchema(self):
|
|
123
|
+
raise Exception("Can not create knowledge graph, schema missing")
|
|
124
|
+
|
|
125
|
+
def _create(self) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Create knowledge graph
|
|
128
|
+
Extract entities and relations from sources
|
|
129
|
+
"""
|
|
130
|
+
self.client = OpenAI()
|
|
131
|
+
|
|
132
|
+
if self.schema is not None:
|
|
133
|
+
self._createKGWithSchema()
|
|
134
|
+
else:
|
|
135
|
+
self._createKGWithoutSchema()
|
|
136
|
+
|
|
137
|
+
def ask(self, question:str, history:list|None=None) -> tuple[str, list]:
|
|
138
|
+
"""
|
|
139
|
+
Query the knowledge graph using natural language
|
|
140
|
+
if the query is asked as part of a longer conversation make sure to
|
|
141
|
+
include past history.
|
|
142
|
+
|
|
143
|
+
Rreturns:
|
|
144
|
+
tuple[str, list]: answer, conversation history
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
>>> ans, history = kg.ask("Which actor has the most oscars")
|
|
148
|
+
>>> ans, history = kg.ask("List a few movies in which that actored played in", history)
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
if history is None:
|
|
152
|
+
history = []
|
|
153
|
+
|
|
154
|
+
answer = query(self, question, history, self.model)
|
|
155
|
+
return (answer, history)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
ONTOLOGY_DETECTION_SYSTEM_PROMPT = """Create SQL statement which will capture the different entities and the relations between them in the provided text.
|
|
2
|
+
Each table should represent either a single entity type with the entity's attributes as the table columns or a relation between two entities, using foreign keys.
|
|
3
|
+
For example an IMDB document is likely to produce 5 tables: Movie, Actor and Director each with its own columns and ACTED and DIRECTED tables.
|
|
4
|
+
Do not create an ID primary key column but set the most compelling attribute as the entity's primary key
|
|
5
|
+
Make sure to call the run_sql function provided in your tools set for each sql statement generated."""
|
|
6
|
+
|
|
7
|
+
KNOWLEDGE_EXTRACTION_SYSTEM_PROMPT = """You are a helpful assistant with the goal of extracting
|
|
8
|
+
entities and relationships from text"""
|
|
9
|
+
|
|
10
|
+
SQL_FOLDING_PROMPT = """The following two CREATE TABLE SQL statments are
|
|
11
|
+
creating the same table name, consolidate the two into a single
|
|
12
|
+
CREATE TABLE SQL statment.
|
|
13
|
+
Remove all duplicated columns.
|
|
14
|
+
Make sure the number of PRIMARY KEYs and FOREIGN KEYs is maintained.
|
|
15
|
+
Reply only with the result SQL statment, the reply should start with:
|
|
16
|
+
CREATE TABLE and end with );
|
|
17
|
+
For example: given the two CREATE TABLE SQL:
|
|
18
|
+
CREATE TABLE Person (FirstName string, LastName string, Age int);
|
|
19
|
+
CREATE TABLE Person (SurName string, ForeName string, Height int);
|
|
20
|
+
The response should be:
|
|
21
|
+
CREATE TABLE Person (FirstName string, LastName string, Age int, Height int);
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
SQL_FOCUS_PROMPT = """Given a CREATE TABLE SQL statment the table name
|
|
25
|
+
represents some entity, in case the number of columns in the table is
|
|
26
|
+
greater than 5, remove columns which are less descriptive of the entity.
|
|
27
|
+
Do NOT remove columns which are either a PRIMARY KEY or a FOREIGN KEY.
|
|
28
|
+
Make sure to leave at least 5 columns.
|
|
29
|
+
Do NOT rename the table name.
|
|
30
|
+
Reply only with the result SQL statment, the reply should start with:
|
|
31
|
+
CREATE TABLE and end with );
|
|
32
|
+
For example: given the CREATE TABLE SQL:
|
|
33
|
+
CREATE TABLE Movie (
|
|
34
|
+
Title string PRIMARY KEY,
|
|
35
|
+
Genre string,
|
|
36
|
+
ReleaseYear int,
|
|
37
|
+
Rating string,
|
|
38
|
+
Language string,
|
|
39
|
+
BoxOffice string,
|
|
40
|
+
streaming_date STRING,
|
|
41
|
+
runtime STRING
|
|
42
|
+
);
|
|
43
|
+
The response should be:
|
|
44
|
+
CREATE TABLE Movie (
|
|
45
|
+
Title string PRIMARY KEY,
|
|
46
|
+
Genre string,
|
|
47
|
+
ReleaseYear int,
|
|
48
|
+
Rating string,
|
|
49
|
+
runtime STRING
|
|
50
|
+
);
|
|
51
|
+
Another example: given the CREATE TABLE SQL:
|
|
52
|
+
CREATE TABLE Episode (
|
|
53
|
+
show_title VARCHAR(255),
|
|
54
|
+
show_year VARCHAR(4),
|
|
55
|
+
season INT,
|
|
56
|
+
episode_number INT,
|
|
57
|
+
episode_title VARCHAR(255),
|
|
58
|
+
air_date DATE,
|
|
59
|
+
episode_rating DECIMAL(2, 1),
|
|
60
|
+
FOREIGN KEY (show_title, show_year) REFERENCES TVShow(title, year)
|
|
61
|
+
);
|
|
62
|
+
You are not allowed to remove either show_title or show_year as these are part of the table's
|
|
63
|
+
foreign key.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
RELATIONSHIP_NAMING_PROMPT = """"You are a Cypher expert, help suggest names for
|
|
67
|
+
relationship types connecting source node to destination node.
|
|
68
|
+
Respond only with the suggested name.
|
|
69
|
+
For example given source node of type 'Actor' and destination node of type 'Movie'
|
|
70
|
+
Replay with: ACTED"""
|
|
71
|
+
|
|
72
|
+
FIND_UNIQUE_ATTRIBUTE_PROMPT = """From the list of attributes describing a graph node
|
|
73
|
+
pick the most suitble attribute to act as a unique identifier of the node.
|
|
74
|
+
Respond only with the suggested attribute name
|
|
75
|
+
For example given a list of attributes: series_title, air_date, season_number, episode_number, title
|
|
76
|
+
Describing the graph entity "Episode" Reply with: title"""
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from openai import OpenAI
|
|
3
|
+
|
|
4
|
+
# Equipet assistant with tools:
|
|
5
|
+
tools = [
|
|
6
|
+
{
|
|
7
|
+
"type": "function",
|
|
8
|
+
"function": {
|
|
9
|
+
"name": "run_cypher_query",
|
|
10
|
+
"description": "Run a Cypher query against the graph",
|
|
11
|
+
"parameters": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"properties": {
|
|
14
|
+
"query": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "query to execute",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
"required": ["query"],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
def run_cypher_query(g, q):
|
|
26
|
+
return g.query(q).result_set
|
|
27
|
+
|
|
28
|
+
# convert a schema into a string representation
|
|
29
|
+
def _graph_schema_to_prompt(s) -> str:
|
|
30
|
+
# Build a string description of graph schema
|
|
31
|
+
desc = "The knowladge graph schema is as follows:\n"
|
|
32
|
+
|
|
33
|
+
#--------------------------------------------------------------------------
|
|
34
|
+
# Describe labels
|
|
35
|
+
#--------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
# list labels
|
|
38
|
+
desc = desc + "The graph contains the following node labels:\n";
|
|
39
|
+
for e in s.entities:
|
|
40
|
+
desc += e + "\n"
|
|
41
|
+
|
|
42
|
+
# specify attributes associated with each entity
|
|
43
|
+
for e in s.entities:
|
|
44
|
+
e = s.entities[e]
|
|
45
|
+
|
|
46
|
+
lbl_name = e.name
|
|
47
|
+
attributes = e.attributes
|
|
48
|
+
attr_count = len(attributes)
|
|
49
|
+
|
|
50
|
+
if (attr_count == 0):
|
|
51
|
+
desc += f"The {lbl_name} label has no attributes\n"
|
|
52
|
+
else:
|
|
53
|
+
desc += f"The {lbl_name} label is associated with the following attribute(s):\n"
|
|
54
|
+
for attr in attributes:
|
|
55
|
+
attr_name = attr.name
|
|
56
|
+
attr_type = attr.type.__name__
|
|
57
|
+
desc += f"{attr_name} which is of type {attr_type}\n"
|
|
58
|
+
|
|
59
|
+
desc += "The graph contains the following relationship types:\n"
|
|
60
|
+
|
|
61
|
+
#--------------------------------------------------------------------------
|
|
62
|
+
# Describe relationships
|
|
63
|
+
#--------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
# list relations
|
|
66
|
+
for rel in s.relations:
|
|
67
|
+
rel = s.relations[rel]
|
|
68
|
+
|
|
69
|
+
src = rel.src.name
|
|
70
|
+
relationship_type = rel.name
|
|
71
|
+
dest = rel.dest.name
|
|
72
|
+
desc += f"{relationship_type}\n"
|
|
73
|
+
desc += f"The {relationship_type} relationship connects the following labels:\n"
|
|
74
|
+
desc += f"{src} is connected via {relationship_type} to {dest}\n"
|
|
75
|
+
|
|
76
|
+
desc += "This is the end of the knowladge graph schema description.\n"
|
|
77
|
+
|
|
78
|
+
return desc;
|
|
79
|
+
|
|
80
|
+
def run_conversation(g, question, messages, model) -> str:
|
|
81
|
+
client = OpenAI()
|
|
82
|
+
|
|
83
|
+
# Step 1: send the conversation and available functions to the model
|
|
84
|
+
messages.append({"role": "user", "content": question})
|
|
85
|
+
|
|
86
|
+
response = client.chat.completions.create(
|
|
87
|
+
model=model,
|
|
88
|
+
messages=messages,
|
|
89
|
+
tools=tools,
|
|
90
|
+
tool_choice="auto", # auto is default, but we'll be explicit
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
response_message = response.choices[0].message
|
|
94
|
+
tool_calls = response_message.tool_calls
|
|
95
|
+
|
|
96
|
+
# Step 2: check if the model wanted to call a function
|
|
97
|
+
if tool_calls:
|
|
98
|
+
# Step 3: call the function
|
|
99
|
+
# Note: the JSON response may not always be valid; be sure to handle errors
|
|
100
|
+
available_functions = {
|
|
101
|
+
"run_cypher_query": run_cypher_query,
|
|
102
|
+
} # only one function in this example, but you can have multiple
|
|
103
|
+
messages.append(response_message) # extend conversation with assistant's reply
|
|
104
|
+
# Step 4: send the info for each function call and function response to the model
|
|
105
|
+
for tool_call in tool_calls:
|
|
106
|
+
function_name = tool_call.function.name
|
|
107
|
+
function_to_call = available_functions[function_name]
|
|
108
|
+
function_args = json.loads(tool_call.function.arguments)
|
|
109
|
+
q = function_args.get("query")
|
|
110
|
+
print(f"OpenAI suggested query: {q}")
|
|
111
|
+
function_response = function_to_call(g, q)
|
|
112
|
+
messages.append(
|
|
113
|
+
{
|
|
114
|
+
"tool_call_id": tool_call.id,
|
|
115
|
+
"role": "tool",
|
|
116
|
+
"name": function_name,
|
|
117
|
+
"content": str(function_response),
|
|
118
|
+
}
|
|
119
|
+
) # extend conversation with function response
|
|
120
|
+
second_response = client.chat.completions.create(
|
|
121
|
+
model=model,
|
|
122
|
+
messages=messages,
|
|
123
|
+
) # get a new response from the model where it can see the function response
|
|
124
|
+
|
|
125
|
+
return second_response.choices[0].message.content
|
|
126
|
+
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
def query(kg, question:str, messages:list, model="gpt-4-1106-preview") -> str:
|
|
130
|
+
# Build system message
|
|
131
|
+
if len(messages) == 0:
|
|
132
|
+
# graph schema
|
|
133
|
+
schema_desc = _graph_schema_to_prompt(kg.schema)
|
|
134
|
+
messages.append(
|
|
135
|
+
{"role": "system", "content": f"""You are a graph database expert fluent in the Cypher query language
|
|
136
|
+
with access to the following knowledge graph: {schema_desc}"""})
|
|
137
|
+
|
|
138
|
+
# Get an answer
|
|
139
|
+
answer = run_conversation(kg.graph, question, messages, model)
|
|
140
|
+
|
|
141
|
+
# Append answer to conversation
|
|
142
|
+
messages.append({"role": "assistant", "content": answer})
|
|
143
|
+
|
|
144
|
+
return answer
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .schema import Schema
|