ego commited on
Commit
ba7bcd3
·
1 Parent(s): ea8f8db
.env ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ GOOGLE_API_KEY=AIzaSyBapGOjPJR58TlTMYcnz7G1jP8fsJXZ1Tg
2
+ NV_API_KEY=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
.streamlit/config.toml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ [server]
2
+ headless = true
3
+
4
+ [browser]
5
+ gatherUsageStats = false
Dockerfile CHANGED
@@ -8,4 +8,4 @@ RUN apt-get update && apt-get install -y graphviz
8
 
9
  COPY . .
10
 
11
- CMD ["streamlit", "run", "app.py", "--server.port", "7860", "--server.address", "0.0.0.0"]
 
8
 
9
  COPY . .
10
 
11
+ CMD ["streamlit", "run", "app.py", "--server.port", "7860", "--server.address", "0.0.0.0","--server.enableCORS=false", "--server.enableXsrfProtection=false"]
__pycache__/prompts.cpython-312.pyc ADDED
Binary file (6.97 kB). View file
 
agent_workflow.png ADDED
app.py CHANGED
@@ -74,6 +74,8 @@ if "deep_summary" not in st.session_state:
74
  st.session_state.deep_summary = None
75
  if "graph_dot" not in st.session_state:
76
  st.session_state.graph_dot = None
 
 
77
 
78
  def switch_page(page_name):
79
  st.session_state.page = page_name
@@ -244,7 +246,7 @@ def view_summary_dialog(text):
244
 
245
  @dialog_decorator("Knowledge Graph Visualization", width="large")
246
  def view_graph_dialog(dot_code):
247
- st.graphviz_chart(dot_code, width='stretch')
248
  st.caption("Right-click -> 'Open Image in New Tab' to zoom/download.")
249
 
250
  def show_app():
@@ -288,6 +290,7 @@ def show_app():
288
  st.session_state.full_text = ""
289
  st.session_state.processed_files = set()
290
  st.session_state.upload_status = ""
 
291
  st.session_state.uploader_key += 1
292
  st.rerun()
293
 
@@ -301,6 +304,10 @@ def show_app():
301
  # Chat History
302
  for msg in st.session_state.messages:
303
  with st.chat_message(msg["role"]):
 
 
 
 
304
  st.markdown(msg["content"])
305
 
306
  # User Input
@@ -313,22 +320,26 @@ def show_app():
313
  if st.session_state.agent:
314
  # Container for intermediate thought process
315
  with st.status("Agent Reasoning...", expanded=True) as status:
 
316
 
317
  def graph_callback(node_name, state):
318
- if node_name == "expand_query":
319
- status.write(f"🧠 **Expanding Query** with related concepts...")
320
- elif node_name == "retrieve":
321
- status.write(f"🔍 **Retrieving** context for query: *'{state.get('current_query', '...')}'*")
322
  elif node_name == "generate":
323
- status.write("🧠 **Generating** answer...")
324
  elif node_name == "reflect":
325
  score = state.get("reflection_score")
326
  if score == "yes":
327
- status.write("✅ **Reflection Passed**: Answer is grounded.")
328
  else:
329
- status.write("❌ **Reflection Failed**: Hallucination/Irrelevance detected.")
330
  elif node_name == "rewrite_query":
331
- status.write(f"🔄 **Rewriting Query** to improve results...")
 
 
 
 
332
 
333
  result = st.session_state.agent.run(prompt, callback=graph_callback)
334
  status.update(label="Response Ready", state="complete", expanded=False)
@@ -336,11 +347,15 @@ def show_app():
336
  response = result["generation"]
337
 
338
  # Show debug steps comfortably (Optional redundant info, maybe keep for final stats)
339
- with st.expander("⛓️ Final Stats", expanded=False):
340
  st.write(f"**Reflected:** {result.get('reflection_score')} | **Total Iter:** {result.get('iterations')}")
341
 
342
  st.markdown(response)
343
- st.session_state.messages.append({"role": "assistant", "content": response})
 
 
 
 
344
  else:
345
  st.warning("Please upload a PDF first.")
346
 
@@ -365,22 +380,24 @@ def show_app():
365
 
366
  # Podcast Tool
367
  with st.expander("🎧 Podcast", expanded=False):
368
- if st.button("Generate Audio"):
369
- briefing = ensure_deep_summary()
370
-
371
- with st.spinner("Scripting & Synthesizing..."):
372
- p_gen = PodcastGenerator()
373
- script = p_gen.generate_audio_script(briefing)
374
-
375
- # Show script preview
376
- st.caption("Dialogue generated.")
377
-
378
- # Generate Audio
379
- audio_path = p_gen.generate_audio_file(script)
380
- if audio_path:
381
- st.audio(audio_path)
382
- else:
383
- st.error("Audio generation failed.")
 
 
384
 
385
  # Knowledge Graph Tool
386
  with st.expander("🕸️ Knowledge Graph", expanded=False):
 
74
  st.session_state.deep_summary = None
75
  if "graph_dot" not in st.session_state:
76
  st.session_state.graph_dot = None
77
+ if "podcast_audio" not in st.session_state:
78
+ st.session_state.podcast_audio = None
79
 
80
  def switch_page(page_name):
81
  st.session_state.page = page_name
 
246
 
247
  @dialog_decorator("Knowledge Graph Visualization", width="large")
248
  def view_graph_dialog(dot_code):
249
+ st.graphviz_chart(dot_code, width="stretch")
250
  st.caption("Right-click -> 'Open Image in New Tab' to zoom/download.")
251
 
252
  def show_app():
 
290
  st.session_state.full_text = ""
291
  st.session_state.processed_files = set()
292
  st.session_state.upload_status = ""
293
+ st.session_state.podcast_audio = None
294
  st.session_state.uploader_key += 1
295
  st.rerun()
296
 
 
304
  # Chat History
305
  for msg in st.session_state.messages:
306
  with st.chat_message(msg["role"]):
307
+ if "thoughts" in msg and msg["thoughts"]:
308
+ with st.expander("⛓️ Reasoning Log", expanded=False):
309
+ for log in msg["thoughts"]:
310
+ st.write(log)
311
  st.markdown(msg["content"])
312
 
313
  # User Input
 
320
  if st.session_state.agent:
321
  # Container for intermediate thought process
322
  with st.status("Agent Reasoning...", expanded=True) as status:
323
+ thoughts = []
324
 
325
  def graph_callback(node_name, state):
326
+ msg = ""
327
+ if node_name == "retrieve":
328
+ msg = f"🔍 **Retrieving** context for query: *'{state.get('current_query', '...')}'*"
 
329
  elif node_name == "generate":
330
+ msg = "🧠 **Generating** answer..."
331
  elif node_name == "reflect":
332
  score = state.get("reflection_score")
333
  if score == "yes":
334
+ msg = "✅ **Reflection Passed**: Answer is grounded."
335
  else:
336
+ msg = "❌ **Reflection Failed**: Hallucination/Irrelevance detected."
337
  elif node_name == "rewrite_query":
338
+ msg = f"🔄 **Rewriting Query** to improve results..."
339
+
340
+ if msg:
341
+ status.write(msg)
342
+ thoughts.append(msg)
343
 
344
  result = st.session_state.agent.run(prompt, callback=graph_callback)
345
  status.update(label="Response Ready", state="complete", expanded=False)
 
347
  response = result["generation"]
348
 
349
  # Show debug steps comfortably (Optional redundant info, maybe keep for final stats)
350
+ with st.expander("📊 Final Stats", expanded=False):
351
  st.write(f"**Reflected:** {result.get('reflection_score')} | **Total Iter:** {result.get('iterations')}")
352
 
353
  st.markdown(response)
354
+ st.session_state.messages.append({
355
+ "role": "assistant",
356
+ "content": response,
357
+ "thoughts": thoughts
358
+ })
359
  else:
360
  st.warning("Please upload a PDF first.")
361
 
 
380
 
381
  # Podcast Tool
382
  with st.expander("🎧 Podcast", expanded=False):
383
+ if not st.session_state.podcast_audio:
384
+ if st.button("Generate Audio"):
385
+ briefing = ensure_deep_summary()
386
+ with st.spinner("Scripting & Synthesizing..."):
387
+ p_gen = PodcastGenerator()
388
+ script = p_gen.generate_audio_script(briefing)
389
+ audio_path = p_gen.generate_audio_file(script)
390
+ if audio_path:
391
+ st.session_state.podcast_audio = audio_path
392
+ st.rerun()
393
+ else:
394
+ st.error("Audio generation failed.")
395
+ else:
396
+ st.success("Podcast Ready!")
397
+ st.audio(st.session_state.podcast_audio)
398
+ if st.button("🔄 Regenerate Podcast"):
399
+ st.session_state.podcast_audio = None
400
+ st.rerun()
401
 
402
  # Knowledge Graph Tool
403
  with st.expander("🕸️ Knowledge Graph", expanded=False):
core/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (122 Bytes). View file
 
core/__pycache__/graph.cpython-312.pyc ADDED
Binary file (6.37 kB). View file
 
core/__pycache__/map_reduce.cpython-312.pyc ADDED
Binary file (1.99 kB). View file
 
core/__pycache__/models.cpython-312.pyc ADDED
Binary file (3.7 kB). View file
 
core/__pycache__/pdf_processer.cpython-312.pyc ADDED
Binary file (4.13 kB). View file
 
core/__pycache__/podcast.cpython-312.pyc ADDED
Binary file (3.09 kB). View file
 
core/__pycache__/visualizer.cpython-312.pyc ADDED
Binary file (1.35 kB). View file
 
core/graph.py CHANGED
@@ -2,7 +2,7 @@ from typing import TypedDict, List
2
  from langgraph.graph import StateGraph, END
3
  from langchain_core.documents import Document
4
  from core.models import get_llm
5
- from prompts import RAG_PROMPT, REFLECTION_PROMPT, REWRITE_PROMPT, QUERY_EXPANSION_PROMPT
6
  from langchain_core.output_parsers import StrOutputParser
7
 
8
  class GraphState(TypedDict):
@@ -19,12 +19,6 @@ class RAGAgent:
19
  self.llm = get_llm()
20
  self.app = self.build_graph()
21
 
22
- def expand_query(self, state: GraphState):
23
- question = state["question"]
24
- chain = QUERY_EXPANSION_PROMPT | self.llm | StrOutputParser()
25
- expanded_query = chain.invoke({"question": question})
26
- return {"current_query": expanded_query}
27
-
28
  def retrieve(self, state: GraphState):
29
  query = state["current_query"]
30
  docs = self.retriever.invoke(query)
@@ -44,9 +38,17 @@ class RAGAgent:
44
  def reflect(self, state: GraphState):
45
  question = state["question"]
46
  generation = state["generation"]
 
 
 
 
47
 
48
  chain = REFLECTION_PROMPT | self.llm | StrOutputParser()
49
- score = chain.invoke({"question": question, "generation": generation})
 
 
 
 
50
 
51
  # Normalize score
52
  normalized_score = "yes" if "yes" in score.lower() else "no"
@@ -54,9 +56,15 @@ class RAGAgent:
54
 
55
  def rewrite_query(self, state: GraphState):
56
  question = state["question"]
 
 
57
 
58
  chain = REWRITE_PROMPT | self.llm | StrOutputParser()
59
- new_query = chain.invoke({"question": question})
 
 
 
 
60
 
61
  return {"current_query": new_query, "iterations": state["iterations"] + 1}
62
 
@@ -73,15 +81,13 @@ class RAGAgent:
73
  workflow = StateGraph(GraphState)
74
 
75
  # Define Nodes
76
- workflow.add_node("expand_query", self.expand_query)
77
  workflow.add_node("retrieve", self.retrieve)
78
  workflow.add_node("generate", self.generate)
79
  workflow.add_node("reflect", self.reflect)
80
  workflow.add_node("rewrite_query", self.rewrite_query)
81
 
82
  # Build Edges
83
- workflow.set_entry_point("expand_query")
84
- workflow.add_edge("expand_query", "retrieve")
85
  workflow.add_edge("retrieve", "generate")
86
  workflow.add_edge("generate", "reflect")
87
 
 
2
  from langgraph.graph import StateGraph, END
3
  from langchain_core.documents import Document
4
  from core.models import get_llm
5
+ from prompts import RAG_PROMPT, REFLECTION_PROMPT, REWRITE_PROMPT
6
  from langchain_core.output_parsers import StrOutputParser
7
 
8
  class GraphState(TypedDict):
 
19
  self.llm = get_llm()
20
  self.app = self.build_graph()
21
 
 
 
 
 
 
 
22
  def retrieve(self, state: GraphState):
23
  query = state["current_query"]
24
  docs = self.retriever.invoke(query)
 
38
  def reflect(self, state: GraphState):
39
  question = state["question"]
40
  generation = state["generation"]
41
+ docs = state["documents"]
42
+
43
+ # Format context so the reflector can check for grounding
44
+ context = "\n\n".join([f"[Source: {doc.metadata.get('filename', 'Unknown')}] {doc.page_content}" for doc in docs])
45
 
46
  chain = REFLECTION_PROMPT | self.llm | StrOutputParser()
47
+ score = chain.invoke({
48
+ "context": context,
49
+ "question": question,
50
+ "generation": generation
51
+ })
52
 
53
  # Normalize score
54
  normalized_score = "yes" if "yes" in score.lower() else "no"
 
56
 
57
  def rewrite_query(self, state: GraphState):
58
  question = state["question"]
59
+ previous_query = state["current_query"]
60
+ failed_gen = state["generation"]
61
 
62
  chain = REWRITE_PROMPT | self.llm | StrOutputParser()
63
+ new_query = chain.invoke({
64
+ "question": question,
65
+ "previous_query": previous_query,
66
+ "generation": failed_gen
67
+ })
68
 
69
  return {"current_query": new_query, "iterations": state["iterations"] + 1}
70
 
 
81
  workflow = StateGraph(GraphState)
82
 
83
  # Define Nodes
 
84
  workflow.add_node("retrieve", self.retrieve)
85
  workflow.add_node("generate", self.generate)
86
  workflow.add_node("reflect", self.reflect)
87
  workflow.add_node("rewrite_query", self.rewrite_query)
88
 
89
  # Build Edges
90
+ workflow.set_entry_point("retrieve")
 
91
  workflow.add_edge("retrieve", "generate")
92
  workflow.add_edge("generate", "reflect")
93
 
core/models.py CHANGED
@@ -1,7 +1,8 @@
1
  import os
2
  import streamlit as st
3
  from langchain_nvidia_ai_endpoints import ChatNVIDIA
4
- from langchain_google_genai import GoogleGenerativeAIEmbeddings
 
5
 
6
  def get_llm(model_name: str = "nvidia/nemotron-3-nano-30b-a3b"):
7
  """
@@ -37,3 +38,51 @@ def get_embeddings():
37
  raise ValueError("GOOGLE_API_KEY not found in environment or secrets.")
38
 
39
  return GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_api_key=api_key)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import streamlit as st
3
  from langchain_nvidia_ai_endpoints import ChatNVIDIA
4
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI
5
+ from google import genai
6
 
7
  def get_llm(model_name: str = "nvidia/nemotron-3-nano-30b-a3b"):
8
  """
 
38
  raise ValueError("GOOGLE_API_KEY not found in environment or secrets.")
39
 
40
  return GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_api_key=api_key)
41
+
42
+ from google.genai import types
43
+
44
+
45
+ def generate_podcast_audio(script_text: str):
46
+ """
47
+ Calls Gemini TTS with multi-speaker configuration.
48
+ Returns raw audio data.
49
+ """
50
+ api_key = os.getenv("GOOGLE_API_KEY")
51
+ if not api_key and "GOOGLE_API_KEY" in st.secrets:
52
+ api_key = st.secrets["GOOGLE_API_KEY"]
53
+
54
+ client = genai.Client(api_key=api_key)
55
+
56
+ response = client.models.generate_content(
57
+ model="gemini-2.5-flash-preview-tts",
58
+ contents=f"Generate a podcast dialogue audio. \n\n{script_text}",
59
+ config=types.GenerateContentConfig(
60
+ response_modalities=["AUDIO"],
61
+ speech_config=types.SpeechConfig(
62
+ multi_speaker_voice_config=types.MultiSpeakerVoiceConfig(
63
+ speaker_voice_configs=[
64
+ types.SpeakerVoiceConfig(
65
+ speaker='Alex',
66
+ voice_config=types.VoiceConfig(
67
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(
68
+ voice_name='Kore',
69
+ )
70
+ )
71
+ ),
72
+ types.SpeakerVoiceConfig(
73
+ speaker='Jamie',
74
+ voice_config=types.VoiceConfig(
75
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(
76
+ voice_name='Puck',
77
+ )
78
+ )
79
+ ),
80
+ ]
81
+ )
82
+ )
83
+ )
84
+ )
85
+
86
+ if response.candidates and response.candidates[0].content.parts:
87
+ return response.candidates[0].content.parts[0].inline_data.data
88
+ return None
core/pdf_processer.py CHANGED
@@ -76,7 +76,6 @@ class PDFProcessor:
76
  def get_retriever(self):
77
  if not self.vector_store:
78
  raise ValueError("Vector store not initialized. Upload a PDF first.")
79
- # return self.vector_store.as_retriever(search_type="mmr", search_kwargs={"k": 5})
80
  return self.vector_store.as_retriever(search_type="mmr", search_kwargs={"k": 5})
81
  def get_full_text(self):
82
  return "\n\n".join([doc.page_content for doc in self.documents])
 
76
  def get_retriever(self):
77
  if not self.vector_store:
78
  raise ValueError("Vector store not initialized. Upload a PDF first.")
 
79
  return self.vector_store.as_retriever(search_type="mmr", search_kwargs={"k": 5})
80
  def get_full_text(self):
81
  return "\n\n".join([doc.page_content for doc in self.documents])
core/podcast.py CHANGED
@@ -1,13 +1,9 @@
1
- from core.models import get_llm
2
  from prompts import PODCAST_AUDIO_PROMPT
3
  from langchain_core.prompts import ChatPromptTemplate
4
  from langchain_core.output_parsers import StrOutputParser
5
  import tempfile
6
- import os
7
- from google import genai
8
- from google.genai import types
9
  import wave
10
- import streamlit as st
11
 
12
  class PodcastGenerator:
13
  def __init__(self):
@@ -25,52 +21,12 @@ class PodcastGenerator:
25
 
26
  def generate_audio_file(self, script_text):
27
  """
28
- Uses 'gemini-2.5-flash-preview-tts' with official google-genai SDK for multi-speaker.
29
  """
30
- api_key = os.getenv("GOOGLE_API_KEY")
31
- if not api_key and "GOOGLE_API_KEY" in st.secrets:
32
- api_key = st.secrets["GOOGLE_API_KEY"]
33
-
34
- if not api_key:
35
- return None
36
-
37
- client = genai.Client(api_key=api_key)
38
-
39
  try:
40
- response = client.models.generate_content(
41
- model="gemini-2.5-flash-preview-tts",
42
- contents=f"Generate a podcast dialogue audio. \n\n{script_text}",
43
- config=types.GenerateContentConfig(
44
- response_modalities=["AUDIO"],
45
- speech_config=types.SpeechConfig(
46
- multi_speaker_voice_config=types.MultiSpeakerVoiceConfig(
47
- speaker_voice_configs=[
48
- types.SpeakerVoiceConfig(
49
- speaker='Alex',
50
- voice_config=types.VoiceConfig(
51
- prebuilt_voice_config=types.PrebuiltVoiceConfig(
52
- voice_name='Kore',
53
- )
54
- )
55
- ),
56
- types.SpeakerVoiceConfig(
57
- speaker='Jamie',
58
- voice_config=types.VoiceConfig(
59
- prebuilt_voice_config=types.PrebuiltVoiceConfig(
60
- voice_name='Puck',
61
- )
62
- )
63
- ),
64
- ]
65
- )
66
- )
67
- )
68
- )
69
-
70
- # Extract audio data
71
- if response.candidates and response.candidates[0].content.parts:
72
- data = response.candidates[0].content.parts[0].inline_data.data
73
-
74
  # Use NamedTemporaryFile to get a unique name, then close it immediately
75
  # so wave.open can re-open it for writing.
76
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
 
1
+ from core.models import get_llm, generate_podcast_audio
2
  from prompts import PODCAST_AUDIO_PROMPT
3
  from langchain_core.prompts import ChatPromptTemplate
4
  from langchain_core.output_parsers import StrOutputParser
5
  import tempfile
 
 
 
6
  import wave
 
7
 
8
  class PodcastGenerator:
9
  def __init__(self):
 
21
 
22
  def generate_audio_file(self, script_text):
23
  """
24
+ Uses centralized Gemini TTS logic.
25
  """
 
 
 
 
 
 
 
 
 
26
  try:
27
+ data = generate_podcast_audio(script_text)
28
+
29
+ if data:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  # Use NamedTemporaryFile to get a unique name, then close it immediately
31
  # so wave.open can re-open it for writing.
32
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
core/visualizer.py CHANGED
@@ -3,7 +3,7 @@ from langchain_core.prompts import PromptTemplate
3
  from langchain_core.output_parsers import StrOutputParser
4
  import graphviz
5
  import re
6
- from prompts import GRAPH_TEMPLATE
7
 
8
  class KnowledgeGraphGenerator:
9
  def __init__(self):
@@ -13,7 +13,7 @@ class KnowledgeGraphGenerator:
13
  # Text is now the "Deep Summary", so no need to truncate.
14
  input_text = text
15
 
16
- chain = PromptTemplate.from_template(GRAPH_TEMPLATE) | self.llm | StrOutputParser()
17
  dot_code = chain.invoke({"text": input_text})
18
 
19
  # Cleanup markdown if present
 
3
  from langchain_core.output_parsers import StrOutputParser
4
  import graphviz
5
  import re
6
+ from prompts import GRAPH_PROMPT
7
 
8
  class KnowledgeGraphGenerator:
9
  def __init__(self):
 
13
  # Text is now the "Deep Summary", so no need to truncate.
14
  input_text = text
15
 
16
+ chain = GRAPH_PROMPT | self.llm | StrOutputParser()
17
  dot_code = chain.invoke({"text": input_text})
18
 
19
  # Cleanup markdown if present
.gitignore → gitignore RENAMED
File without changes
prompts.py CHANGED
@@ -1,13 +1,18 @@
1
  from langchain_core.prompts import ChatPromptTemplate
2
 
3
  # RAG Generation Prompt
4
- RAG_SYSTEM = """You are an academic research assistant. Answer the user's question based strictly on the provided context.
5
  If the context does not contain the answer, say "I cannot answer this based on the document."
6
 
7
  Requirements:
8
  1. Use academic tone.
9
- 2. YOU MUST CITE sources using (Document: <filename>, Page: <page>) format at the end of relevant sentences.
10
- 3. Be concise but comprehensive."""
 
 
 
 
 
11
 
12
  RAG_HUMAN = """Context:
13
  {context}
@@ -24,13 +29,17 @@ RAG_PROMPT = ChatPromptTemplate.from_messages([
24
 
25
  # Reflection Prompt
26
  REFLECTION_SYSTEM = """You are a senior editor grading an AI-generated answer.
27
- Check if the answer is grounded in the provided documents and relevant to the user's question.
 
 
28
 
29
- Output exactly "yes" if the answer is grounded and relevant.
30
- Output "no" if the answer is hallucinated, irrelevant, or incomplete.
31
- IMPORTANT: If the answer says "I cannot answer" or "context does not contain", YOU MUST OUTPUT "no"."""
32
 
33
- REFLECTION_HUMAN = """User Question: {question}
 
 
 
34
  Generated Answer: {generation}
35
 
36
  Current Answer Quality status:"""
@@ -41,59 +50,61 @@ REFLECTION_PROMPT = ChatPromptTemplate.from_messages([
41
  ])
42
 
43
  # Query Rewrite Prompt
44
- REWRITE_SYSTEM = """You are a query optimizer. The previous search query failed to retrieve relevant documents.
45
- Rewrite the user's question to be more specific and keyword-rich for vector retrieval.
 
46
  Output ONLY the rewritten query string."""
47
 
48
  REWRITE_HUMAN = """Original Question: {question}
 
 
49
 
50
- Rewritten Query:"""
51
 
52
  REWRITE_PROMPT = ChatPromptTemplate.from_messages([
53
  ("system", REWRITE_SYSTEM),
54
  ("human", REWRITE_HUMAN)
55
  ])
56
 
57
- # Query Expansion Prompt (Pre-retrieval)
58
- QUERY_EXPANSION_SYSTEM = """You are a research assistant.
59
- Rewrite the user's query to be more effective for vector retrieval (RAG).
60
- - Add 2-3 relevant academic keywords, synonyms, or related concepts.
61
- - Keep the original intent and core subject intact.
62
- - Output ONLY the rewritten/expanded query string. No explanations."""
63
-
64
- QUERY_EXPANSION_HUMAN = "Original Query: {question}\n\nExpanded Query:"
65
-
66
- QUERY_EXPANSION_PROMPT = ChatPromptTemplate.from_messages([
67
- ("system", QUERY_EXPANSION_SYSTEM),
68
- ("human", QUERY_EXPANSION_HUMAN)
69
- ])
70
-
71
  # Podcast Prompts
72
  # Summary/Podcast Map Prompts
73
- SUMMARY_MAP_SYSTEM = """Summarize the following chunk of text for a briefing.
74
- Limit your response to a maximum of 500 words. Focus on key facts, methodology, and results."""
 
 
 
 
 
 
75
 
76
  SUMMARY_MAP_HUMAN = """Text Chunk:
77
  {text}
78
 
79
- Summary:"""
80
 
81
  SUMMARY_MAP_PROMPT = ChatPromptTemplate.from_messages([
82
  ("system", SUMMARY_MAP_SYSTEM),
83
  ("human", SUMMARY_MAP_HUMAN)
84
  ])
85
 
86
- SUMMARY_REDUCE_SYSTEM = """Synthesize the following summaries into a structured "Deep Briefing".
87
- The total length must not exceed 2000 words.
88
- The briefing should have:
89
- 1. Main Theme
90
- 2. Key Findings
91
- 3. Methodology
92
- 4. Implications"""
 
 
 
 
 
 
 
93
 
94
  SUMMARY_REDUCE_PROMPT = ChatPromptTemplate.from_messages([
95
  ("system", SUMMARY_REDUCE_SYSTEM),
96
- ("human", "Summaries:\n{text}\n\nGenerate the briefing.")
97
  ])
98
 
99
  PODCAST_AUDIO_SYSTEM = """You are producing a podcast script.
@@ -123,9 +134,8 @@ PODCAST_AUDIO_PROMPT = ChatPromptTemplate.from_messages([
123
  ])
124
 
125
 
126
-
127
  # Knowledge Graph Prompt
128
- GRAPH_TEMPLATE = """You are an expert at visualizing complex academic information.
129
  Your goal is to extract a DEEP hierarchical structure and key relationships from the provided text and represent them as a CLEAN, multi-level Knowledge Graph using DOT syntax.
130
 
131
  CRITICAL INSTRUCTIONS:
@@ -135,7 +145,7 @@ CRITICAL INSTRUCTIONS:
135
  4. DESCRIPTIVE RELATIONSHIPS: Every edge MUST have a unique, descriptive label (e.g., "implements", "results in", "validates"). AVOID using the same generic label like "includes" for multiple edges in the same branch.
136
  5. AVOID SPIDER WEBS: Focus on hierarchical flow (Root -> Child -> Grandchild) rather than lateral cross-connections.
137
  6. HIERARCHICAL LAYOUT: Use 'rankdir=LR' (Left-to-Right).
138
- 7. CONCISE LABELS: Keep node names and labels short (1-3 words).
139
 
140
  Output ONLY the raw DOT code. No markdown code blocks.
141
 
@@ -154,9 +164,14 @@ digraph G {{
154
  "Category B" -> "Step 1" [label="baseline"];
155
  "Step 1" -> "Validation Method" [label="criteria"];
156
  "Validation Method" -> "Metric X" [label="output"];
157
- }}
158
 
159
- Text to Analyze:
160
  {text}
161
 
162
  DOT Code:"""
 
 
 
 
 
 
1
  from langchain_core.prompts import ChatPromptTemplate
2
 
3
  # RAG Generation Prompt
4
+ RAG_SYSTEM = """You are a research assistant. Answer the user's question based strictly on the provided context.
5
  If the context does not contain the answer, say "I cannot answer this based on the document."
6
 
7
  Requirements:
8
  1. Use academic tone.
9
+ 2. **In-text Citations:** Use Unicode Superscript Numbers (¹, ², ³, ⁴, ⁵, ⁶, ⁷, ⁸, ⁹, ¹⁰) strictly. Place them immediately after the punctuation or relevant phrase.
10
+ - Do NOT use `[^1]` (Markdown footnotes) or `[1]` (Brackets).
11
+ - Example: ...at compile time¹.
12
+ 3. **References Section:** At the very end, include a section titled "References".
13
+ 4. **Reference Format:** List the citations sequentially using normal numbers.
14
+ - Format: `1. Document: <filename>, Page: <page>`
15
+ 5. Be concise but comprehensive."""
16
 
17
  RAG_HUMAN = """Context:
18
  {context}
 
29
 
30
  # Reflection Prompt
31
  REFLECTION_SYSTEM = """You are a senior editor grading an AI-generated answer.
32
+ Evaluate if the answer is:
33
+ 1. GROUNDED: Is the answer supported by the facts in the provided Context?
34
+ 2. RELEVANT: Does it actually answer the User Question?
35
 
36
+ Output exactly "yes" if the answer is both grounded and relevant.
37
+ Output "no" if the answer contains information NOT in the context, is irrelevant, or if the assistant says it cannot answer."""
 
38
 
39
+ REFLECTION_HUMAN = """Context:
40
+ {context}
41
+
42
+ User Question: {question}
43
  Generated Answer: {generation}
44
 
45
  Current Answer Quality status:"""
 
50
  ])
51
 
52
  # Query Rewrite Prompt
53
+ REWRITE_SYSTEM = """You are a query optimizer. The previous search query failed to retrieve documents that could fully answer the question.
54
+ Analyze the original question, the previous query used, and the failed answer to understand what was missing or misunderstood.
55
+ Rewrite the pursuit into a new, improved search query that is more specific and uses better technical keywords.
56
  Output ONLY the rewritten query string."""
57
 
58
  REWRITE_HUMAN = """Original Question: {question}
59
+ Previous Query: {previous_query}
60
+ Failed Answer: {generation}
61
 
62
+ Improved Rewritten Query:"""
63
 
64
  REWRITE_PROMPT = ChatPromptTemplate.from_messages([
65
  ("system", REWRITE_SYSTEM),
66
  ("human", REWRITE_HUMAN)
67
  ])
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  # Podcast Prompts
70
  # Summary/Podcast Map Prompts
71
+ SUMMARY_MAP_SYSTEM = """You are a precision-oriented research analyst.
72
+ Extract atomic facts and technical details from this segment into dense bullet points.
73
+
74
+ STRICT CONSTRAINT: Maximum 500 words total.
75
+ Requirements:
76
+ 1. Highlight core concepts and key relationships (e.g., "A influences B").
77
+ 2. Maintain technical accuracy and preserve specialized terminology.
78
+ 3. Be concise: avoid introductory phrases, focus on pure data/logic."""
79
 
80
  SUMMARY_MAP_HUMAN = """Text Chunk:
81
  {text}
82
 
83
+ Atomic Fact Summary:"""
84
 
85
  SUMMARY_MAP_PROMPT = ChatPromptTemplate.from_messages([
86
  ("system", SUMMARY_MAP_SYSTEM),
87
  ("human", SUMMARY_MAP_HUMAN)
88
  ])
89
 
90
+ SUMMARY_REDUCE_SYSTEM = """You are a Senior Knowledge Architect.
91
+ Synthesize the provided segment summaries into a cohesive, high-density "Master Strategic Briefing".
92
+
93
+ STRICT CONSTRAINT: Total length must be between 1200 and 1800 words for a comprehensive deep-dive.
94
+
95
+ Synthesis Strategy:
96
+ 1. ELIMINATE REDUNDANCY: Group similar findings from different segments together.
97
+ 2. LOGICAL MAPPING: Establish clear connections between methodology, results, and implications across the entire document.
98
+ 3. STRUCTURE: Use professional H2/H3 headers.
99
+ 4. TARGET SECTIONS:
100
+ - Main Themes & Scope
101
+ - Technical Methodology & Contributions
102
+ - Primary Findings & Evidence
103
+ - Critical Implications & "So What?" analysis."""
104
 
105
  SUMMARY_REDUCE_PROMPT = ChatPromptTemplate.from_messages([
106
  ("system", SUMMARY_REDUCE_SYSTEM),
107
+ ("human", "Segment Summaries:\n{text}\n\nExecute the Master Strategic Briefing Summary:")
108
  ])
109
 
110
  PODCAST_AUDIO_SYSTEM = """You are producing a podcast script.
 
134
  ])
135
 
136
 
 
137
  # Knowledge Graph Prompt
138
+ GRAPH_SYSTEM = """You are an expert at visualizing complex knowledge information.
139
  Your goal is to extract a DEEP hierarchical structure and key relationships from the provided text and represent them as a CLEAN, multi-level Knowledge Graph using DOT syntax.
140
 
141
  CRITICAL INSTRUCTIONS:
 
145
  4. DESCRIPTIVE RELATIONSHIPS: Every edge MUST have a unique, descriptive label (e.g., "implements", "results in", "validates"). AVOID using the same generic label like "includes" for multiple edges in the same branch.
146
  5. AVOID SPIDER WEBS: Focus on hierarchical flow (Root -> Child -> Grandchild) rather than lateral cross-connections.
147
  6. HIERARCHICAL LAYOUT: Use 'rankdir=LR' (Left-to-Right).
148
+ 7. CONCISE LABELS: Keep node names and labels short (less than 5 words).
149
 
150
  Output ONLY the raw DOT code. No markdown code blocks.
151
 
 
164
  "Category B" -> "Step 1" [label="baseline"];
165
  "Step 1" -> "Validation Method" [label="criteria"];
166
  "Validation Method" -> "Metric X" [label="output"];
167
+ }}"""
168
 
169
+ GRAPH_HUMAN = """Text to Analyze:
170
  {text}
171
 
172
  DOT Code:"""
173
+
174
+ GRAPH_PROMPT = ChatPromptTemplate.from_messages([
175
+ ("system", GRAPH_SYSTEM),
176
+ ("human", GRAPH_HUMAN)
177
+ ])