Demosthene-OR commited on
Commit
bd1f662
·
1 Parent(s): 343cb0b
Files changed (3) hide show
  1. .gitignore +37 -0
  2. app.py +46 -12
  3. generate_knowledge_graph.py +38 -12
.gitignore ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python temporary files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.pyc
6
+ *.pyo
7
+ *.pyd
8
+
9
+ # Streamlit temporary and config files
10
+ .streamlit/runtime/
11
+ .streamlit/secrets.toml
12
+
13
+ # Virtual environments
14
+ .venv/
15
+ venv/
16
+ env/
17
+ ENV/
18
+ lib/
19
+ bin/
20
+ include/
21
+ share/
22
+
23
+ # Jupyter Notebook files and checkpoints
24
+ *.ipynb
25
+ .ipynb_checkpoints/
26
+
27
+ # Generated HTML graph files (recreated at runtime)
28
+ *.html
29
+
30
+ # Backup files
31
+ *.bak
32
+
33
+ # Environment variables (Crucial for API keys security)
34
+ .env
35
+ .env.local
36
+ .env.*
37
+ !.env.example
app.py CHANGED
@@ -12,6 +12,12 @@ st.set_page_config(
12
 
13
  st.title("Knowledge Graph From Text")
14
 
 
 
 
 
 
 
15
  st.sidebar.title("Input document")
16
  input_method = st.sidebar.radio(
17
  "Choose an input method:",
@@ -32,15 +38,32 @@ if st.sidebar.button("1. Generate Knowledge Graph"):
32
  with st.spinner("Generating knowledge graph..."):
33
  net, graph_docs = generate_knowledge_graph(text)
34
  st.session_state['graph_docs'] = graph_docs
35
- st.success("Knowledge graph generated successfully!")
36
-
37
  output_file = "knowledge_graph.html"
38
  net.save_graph(output_file)
39
- HtmlFile = open(output_file, 'r', encoding='utf-8')
40
- components.html(HtmlFile.read(), height=600)
 
 
 
 
 
 
 
 
 
41
  else:
42
  st.sidebar.error("Please provide some text to generate the graph.")
43
 
 
 
 
 
 
 
 
 
 
44
  # QA Section
45
  if 'graph_docs' in st.session_state:
46
  st.markdown("---")
@@ -50,18 +73,29 @@ if 'graph_docs' in st.session_state:
50
  with col1:
51
  question = st.text_input("Your question :")
52
  with col2:
53
- k_value = st.slider("Relationships to be analyzed (Top K)", min_value=1, max_value=20, value=7)
54
 
55
  if st.button("2. Analyze") and question:
56
  with st.spinner("Semantic search in the current graph..."):
57
- answer, filtered_net = answer_question_with_graph(
58
- question,
59
  st.session_state['graph_docs'],
60
  k_relations=k_value
61
  )
 
 
 
 
 
 
 
 
62
 
63
- st.info(f"**Answer :** {answer}")
64
-
65
- st.markdown("**Subgraph of the relationships used to answer the question :**")
66
- HtmlFile = open("filtered_graph.html", 'r', encoding='utf-8')
67
- components.html(HtmlFile.read(), height=450)
 
 
 
 
 
12
 
13
  st.title("Knowledge Graph From Text")
14
 
15
+ # Initialize session state variables
16
+ if 'graph_version' not in st.session_state:
17
+ st.session_state['graph_version'] = 0
18
+ if 'qa_version' not in st.session_state:
19
+ st.session_state['qa_version'] = 0
20
+
21
  st.sidebar.title("Input document")
22
  input_method = st.sidebar.radio(
23
  "Choose an input method:",
 
38
  with st.spinner("Generating knowledge graph..."):
39
  net, graph_docs = generate_knowledge_graph(text)
40
  st.session_state['graph_docs'] = graph_docs
41
+
 
42
  output_file = "knowledge_graph.html"
43
  net.save_graph(output_file)
44
+ with open(output_file, 'r', encoding='utf-8') as f:
45
+ st.session_state['graph_html'] = f.read()
46
+
47
+ # Increment version to force iframe refresh
48
+ st.session_state['graph_version'] += 1
49
+
50
+ # Reset QA state for new graph
51
+ st.session_state.pop('qa_answer', None)
52
+ st.session_state.pop('qa_html', None)
53
+
54
+ st.success("Knowledge graph generated successfully!")
55
  else:
56
  st.sidebar.error("Please provide some text to generate the graph.")
57
 
58
+ # Display the main graph if it exists in session state
59
+ if 'graph_html' in st.session_state:
60
+ st.subheader("Initial Knowledge Graph")
61
+ # Append version comment to force Streamlit to refresh the iframe when version changes
62
+ components.html(
63
+ st.session_state['graph_html'] + f"<!-- version {st.session_state['graph_version']} -->",
64
+ height=600
65
+ )
66
+
67
  # QA Section
68
  if 'graph_docs' in st.session_state:
69
  st.markdown("---")
 
73
  with col1:
74
  question = st.text_input("Your question :")
75
  with col2:
76
+ k_value = st.slider("Relationships to be analyzed (Top K)", min_value=1, max_value=30, value=15)
77
 
78
  if st.button("2. Analyze") and question:
79
  with st.spinner("Semantic search in the current graph..."):
80
+ answer, filtered_net = answer_question_with_graph( question,
 
81
  st.session_state['graph_docs'],
82
  k_relations=k_value
83
  )
84
+
85
+ st.session_state['qa_answer'] = answer
86
+
87
+ # Read and save the filtered graph HTML content
88
+ with open("filtered_graph.html", 'r', encoding='utf-8') as f:
89
+ st.session_state['qa_html'] = f.read()
90
+
91
+ st.session_state['qa_version'] += 1
92
 
93
+ # Persist the QA results display
94
+ if 'qa_answer' in st.session_state and 'qa_html' in st.session_state:
95
+ st.info(f"**Answer :** {st.session_state['qa_answer']}")
96
+ st.markdown("**Subgraph of the relationships used to answer the question :**")
97
+ # Append version comment to force Streamlit to refresh the iframe when version changes
98
+ components.html(
99
+ st.session_state['qa_html'] + f"<!-- version {st.session_state['qa_version']} -->",
100
+ height=450
101
+ )
generate_knowledge_graph.py CHANGED
@@ -8,11 +8,12 @@ from pyvis.network import Network
8
  from dotenv import load_dotenv
9
  import os
10
  import asyncio
 
11
 
12
  load_dotenv()
13
  api_key = os.getenv("OPENAI_API_KEY")
14
 
15
- llm = ChatOpenAI(temperature=0, model_name="gpt-4o")
16
  graph_transformer = LLMGraphTransformer(llm=llm)
17
 
18
  async def extract_graph_data(text):
@@ -22,14 +23,28 @@ async def extract_graph_data(text):
22
 
23
  def visualize_graph(graph_documents):
24
  net = Network(height="600px", width="100%", directed=True, notebook=False, bgcolor="#222222", font_color="white", filter_menu=True, cdn_resources='remote')
25
- nodes = graph_documents[0].nodes
26
- relationships = graph_documents[0].relationships
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- node_dict = {node.id: node for node in nodes}
29
  valid_edges = []
30
  valid_node_ids = set()
31
 
32
- for rel in relationships:
33
  if rel.source.id in node_dict and rel.target.id in node_dict:
34
  valid_edges.append(rel)
35
  valid_node_ids.update([rel.source.id, rel.target.id])
@@ -41,17 +56,28 @@ def visualize_graph(graph_documents):
41
  except:
42
  continue
43
 
 
 
44
  for rel in valid_edges:
45
- try:
46
- net.add_edge(rel.source.id, rel.target.id, label=rel.type.lower())
47
- except:
48
- continue
 
 
 
49
 
50
  net.set_options('{"physics": {"forceAtlas2Based": {"gravitationalConstant": -100, "centralGravity": 0.01, "springLength": 200, "springConstant": 0.08}, "minVelocity": 0.75, "solver": "forceAtlas2Based"}}')
51
  return net
52
 
 
 
 
 
 
 
53
  def generate_knowledge_graph(text):
54
- graph_documents = asyncio.run(extract_graph_data(text))
55
  net = visualize_graph(graph_documents)
56
  return net, graph_documents
57
 
@@ -61,11 +87,11 @@ def answer_question_with_graph(question, graph_documents, k_relations=7):
61
  all_relationships.extend(doc.relationships)
62
 
63
  if not all_relationships:
64
- return "Aucune relation trouvée dans le graphe.", visualize_graph(graph_documents)
65
 
66
  rel_docs = []
67
  for i, rel in enumerate(all_relationships):
68
- text_rep = f"L'entité '{rel.source.id}' a pour relation '{rel.type}' avec l'entité '{rel.target.id}'."
69
  rel_docs.append(Document(page_content=text_rep, metadata={"rel_index": i}))
70
 
71
  embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
 
8
  from dotenv import load_dotenv
9
  import os
10
  import asyncio
11
+ import streamlit as st
12
 
13
  load_dotenv()
14
  api_key = os.getenv("OPENAI_API_KEY")
15
 
16
+ llm = ChatOpenAI(temperature=0, model_name="gpt-4o-mini")
17
  graph_transformer = LLMGraphTransformer(llm=llm)
18
 
19
  async def extract_graph_data(text):
 
23
 
24
  def visualize_graph(graph_documents):
25
  net = Network(height="600px", width="100%", directed=True, notebook=False, bgcolor="#222222", font_color="white", filter_menu=True, cdn_resources='remote')
26
+
27
+ # Collect all nodes and relationships across all graph documents in the list
28
+ all_nodes = []
29
+ all_relationships = []
30
+ for doc in graph_documents:
31
+ all_nodes.extend(doc.nodes)
32
+ all_relationships.extend(doc.relationships)
33
+
34
+ # Build node dictionary to deduplicate nodes by ID
35
+ node_dict = {node.id: node for node in all_nodes}
36
+
37
+ # Dynamically add any nodes referenced in relationships but missing from nodes list
38
+ for rel in all_relationships:
39
+ if rel.source.id not in node_dict:
40
+ node_dict[rel.source.id] = rel.source
41
+ if rel.target.id not in node_dict:
42
+ node_dict[rel.target.id] = rel.target
43
 
 
44
  valid_edges = []
45
  valid_node_ids = set()
46
 
47
+ for rel in all_relationships:
48
  if rel.source.id in node_dict and rel.target.id in node_dict:
49
  valid_edges.append(rel)
50
  valid_node_ids.update([rel.source.id, rel.target.id])
 
56
  except:
57
  continue
58
 
59
+ # Deduplicate edges to avoid rendering duplicates
60
+ edges_added = set()
61
  for rel in valid_edges:
62
+ edge_key = (rel.source.id, rel.target.id, rel.type.lower())
63
+ if edge_key not in edges_added:
64
+ try:
65
+ net.add_edge(rel.source.id, rel.target.id, label=rel.type.lower())
66
+ edges_added.add(edge_key)
67
+ except:
68
+ continue
69
 
70
  net.set_options('{"physics": {"forceAtlas2Based": {"gravitationalConstant": -100, "centralGravity": 0.01, "springLength": 200, "springConstant": 0.08}, "minVelocity": 0.75, "solver": "forceAtlas2Based"}}')
71
  return net
72
 
73
+
74
+ @st.cache_data
75
+ def get_graph_data_cached(text):
76
+ return asyncio.run(extract_graph_data(text))
77
+
78
+
79
  def generate_knowledge_graph(text):
80
+ graph_documents = get_graph_data_cached(text)
81
  net = visualize_graph(graph_documents)
82
  return net, graph_documents
83
 
 
87
  all_relationships.extend(doc.relationships)
88
 
89
  if not all_relationships:
90
+ return "No relationships found in the graph.", visualize_graph(graph_documents)
91
 
92
  rel_docs = []
93
  for i, rel in enumerate(all_relationships):
94
+ text_rep = f"The entity '{rel.source.id}' has a '{rel.type}' relationship with the entity '{rel.target.id}'."
95
  rel_docs.append(Document(page_content=text_rep, metadata={"rel_index": i}))
96
 
97
  embeddings = OpenAIEmbeddings(model="text-embedding-3-small")