quanho114 commited on
Commit
55f1010
·
1 Parent(s): 5d17903

Add chat mode support - natural responses without MCQ format

Browse files
src/nodes/direct.py CHANGED
@@ -19,7 +19,7 @@ def direct_answer_node(state: GraphState) -> dict:
19
 
20
  llm = get_large_model()
21
 
22
- system_prompt = load_prompt("direct_answer.j2", "system")
23
  user_prompt = load_prompt("direct_answer.j2", "user", question=state["question"], choices=choices_text)
24
 
25
  # Escape curly braces to prevent LangChain from parsing them as variables
@@ -37,6 +37,11 @@ def direct_answer_node(state: GraphState) -> dict:
37
  content = response.content.strip()
38
  print_log(f" [Direct] Reasoning: {content}...")
39
 
 
 
 
 
 
40
  answer = extract_answer(content, num_choices=len(all_choices) or 4)
41
  print_log(f" [Direct] Final Answer: {answer}")
42
  return {"answer": answer, "raw_response": content}
 
19
 
20
  llm = get_large_model()
21
 
22
+ system_prompt = load_prompt("direct_answer.j2", "system", choices=choices_text)
23
  user_prompt = load_prompt("direct_answer.j2", "user", question=state["question"], choices=choices_text)
24
 
25
  # Escape curly braces to prevent LangChain from parsing them as variables
 
37
  content = response.content.strip()
38
  print_log(f" [Direct] Reasoning: {content}...")
39
 
40
+ # Chat mode: return raw response without answer extraction
41
+ if not all_choices:
42
+ print_log(" [Direct] Chat mode - returning natural response")
43
+ return {"answer": "", "raw_response": content}
44
+
45
  answer = extract_answer(content, num_choices=len(all_choices) or 4)
46
  print_log(f" [Direct] Final Answer: {answer}")
47
  return {"answer": answer, "raw_response": content}
src/nodes/logic.py CHANGED
@@ -110,8 +110,9 @@ def logic_solver_node(state: GraphState) -> dict:
110
  all_choices = state["all_choices"]
111
  num_choices = len(all_choices)
112
  choices_text = format_choices(all_choices)
 
113
 
114
- system_prompt = load_prompt("logic_solver.j2", "system")
115
  user_prompt = load_prompt("logic_solver.j2", "user", question=state["question"], choices=choices_text)
116
 
117
  messages: list[BaseMessage] = [
@@ -122,6 +123,13 @@ def logic_solver_node(state: GraphState) -> dict:
122
  step_texts: list[str] = []
123
  computed_outputs: list[str] = []
124
 
 
 
 
 
 
 
 
125
  max_steps = 5
126
  for step in range(max_steps):
127
  response = llm.invoke(messages)
 
110
  all_choices = state["all_choices"]
111
  num_choices = len(all_choices)
112
  choices_text = format_choices(all_choices)
113
+ is_chat_mode = num_choices == 0 # Chat mode when no choices
114
 
115
+ system_prompt = load_prompt("logic_solver.j2", "system", choices=choices_text)
116
  user_prompt = load_prompt("logic_solver.j2", "user", question=state["question"], choices=choices_text)
117
 
118
  messages: list[BaseMessage] = [
 
123
  step_texts: list[str] = []
124
  computed_outputs: list[str] = []
125
 
126
+ # Chat mode: just invoke LLM and return natural response
127
+ if is_chat_mode:
128
+ print_log(" [Logic] Chat mode detected - returning natural response")
129
+ response = llm.invoke(messages)
130
+ content = response.content
131
+ return {"answer": "", "raw_response": content, "route": "math"}
132
+
133
  max_steps = 5
134
  for step in range(max_steps):
135
  response = llm.invoke(messages)
src/nodes/rag.py CHANGED
@@ -1,141 +1,17 @@
1
- """RAG node for knowledge-based question answering with Retrieve & Rerank."""
2
 
3
- import re
4
-
5
- from langchain_core.prompts import ChatPromptTemplate
6
-
7
- from src.config import settings
8
- from src.data_processing.answer import extract_answer
9
- from src.data_processing.formatting import format_choices
10
  from src.state import GraphState
11
- from src.utils.ingestion import get_vector_store
12
- from src.utils.llm import get_small_model
13
  from src.utils.logging import print_log
14
- from src.utils.prompts import load_prompt
15
- from src.nodes.direct import direct_answer_node
16
-
17
-
18
- def _rerank_documents(query: str, docs: list, top_k: int = 3) -> list:
19
- """Rerank retrieved documents using the small LLM.
20
-
21
- Args:
22
- query: The user question
23
- docs: List of retrieved documents
24
- top_k: Number of top documents to return after reranking
25
-
26
- Returns:
27
- List of reranked documents (top_k most relevant)
28
- """
29
- if len(docs) <= top_k:
30
- return docs
31
-
32
- llm = get_small_model()
33
-
34
- # Build document list for reranking prompt
35
- doc_list = ""
36
- for i, doc in enumerate(docs):
37
- content_preview = doc.page_content[:350].replace("\n", " ")
38
- doc_list += f"[{i}] {content_preview}...\n\n"
39
-
40
- rerank_system = (
41
- "/no_think\n"
42
- "Bạn là chuyên gia đánh giá độ liên quan của văn bản. "
43
- "Nhiệm vụ: Chọn ra các đoạn văn bản LIÊN QUAN NHẤT với câu hỏi.\n"
44
- "Chỉ trả về danh sách các số ID (ví dụ: 0, 3, 5), không giải thích."
45
- )
46
-
47
- rerank_user = (
48
- f"Câu hỏi: {query}\n\n"
49
- f"Các đoạn văn bản:\n{doc_list}\n"
50
- f"Hãy chọn {top_k} đoạn văn bản LIÊN QUAN NHẤT với câu hỏi. "
51
- f"Trả về danh sách ID (số từ 0 đến {len(docs)-1}), cách nhau bởi dấu phẩy."
52
- )
53
-
54
- prompt = ChatPromptTemplate.from_messages([
55
- ("system", rerank_system),
56
- ("human", rerank_user),
57
- ])
58
-
59
- try:
60
- chain = prompt | llm
61
- response = chain.invoke({})
62
- content = response.content.strip()
63
- print_log(f" [RAG] Reranker response: {content}")
64
-
65
- # Parse selected IDs from response
66
- selected_ids = []
67
- numbers = re.findall(r'\d+', content)
68
- for num_str in numbers:
69
- idx = int(num_str)
70
- if 0 <= idx < len(docs) and idx not in selected_ids:
71
- selected_ids.append(idx)
72
- if len(selected_ids) >= top_k:
73
- break
74
-
75
- if selected_ids:
76
- reranked = [docs[i] for i in selected_ids]
77
- print_log(f" [RAG] Reranked: selected {len(reranked)} docs from {len(docs)}")
78
- return reranked
79
-
80
- print_log(" [RAG] Rerank parsing failed, using first top_k docs")
81
- return docs[:top_k]
82
-
83
- except Exception as e:
84
- print_log(f" [RAG] Reranking failed: {e}. Using keyword boosting fallback.")
85
- return docs[:top_k]
86
-
87
 
88
 
89
  def knowledge_rag_node(state: GraphState) -> dict:
90
- """Retrieve relevant context, rerank, and answer knowledge-based questions."""
91
- vector_store = get_vector_store()
92
- query = state["question"]
93
- print_log(f" [RAG] Retrieving context for: '{query}'")
94
-
95
- docs = vector_store.similarity_search(query, k=settings.top_k_retrieval)
96
- print_log(f" [RAG] Retrieved {len(docs)} documents")
97
-
98
- if not docs:
99
- print_log(" [Warning] No relevant documents found in Knowledge Base.")
100
- context = ""
101
- else:
102
- reranked_docs = _rerank_documents(query, docs, top_k=settings.top_k_rerank)
103
-
104
- context = "\n\n---\n\n".join([doc.page_content for doc in reranked_docs])
105
-
106
- if reranked_docs:
107
- print_log(f" [RAG] Using {len(reranked_docs)} reranked docs. Top: \"{reranked_docs[0].page_content[:80]}...\"")
108
-
109
- all_choices = state["all_choices"]
110
- choices_text = format_choices(all_choices)
111
-
112
- llm = get_small_model()
113
-
114
- system_prompt = load_prompt("rag.j2", "system", context=context)
115
- user_prompt = load_prompt("rag.j2", "user", question=state["question"], choices=choices_text)
116
-
117
- # Escape curly braces to prevent LangChain from parsing them as variables
118
- system_prompt = system_prompt.replace("{", "{{").replace("}", "}}")
119
- user_prompt = user_prompt.replace("{", "{{").replace("}", "}}")
120
-
121
- prompt = ChatPromptTemplate.from_messages([
122
- ("system", system_prompt),
123
- ("human", user_prompt),
124
- ])
125
-
126
- chain = prompt | llm
127
- response = chain.invoke({})
128
- content = response.content.strip()
129
- print_log(f" [RAG] Reasoning: {content}")
130
-
131
- answer = extract_answer(content, num_choices=len(all_choices) or 4)
132
- print_log(f" [RAG] Final Answer: {answer}")
133
-
134
- # Fallback to direct mode if RAG context was not helpful
135
- if answer is None:
136
- print_log(" [RAG] Context not relevant, falling back to direct mode...")
137
- direct_result = direct_answer_node(state)
138
- direct_result["route"] = "rag->direct" # Track the fallback
139
- return direct_result
140
-
141
- return {"answer": answer, "context": context, "raw_response": content}
 
1
+ """RAG node - Disabled in production (no vector database)."""
2
 
 
 
 
 
 
 
 
3
  from src.state import GraphState
 
 
4
  from src.utils.logging import print_log
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
 
7
  def knowledge_rag_node(state: GraphState) -> dict:
8
+ """
9
+ RAG node disabled in production (no vector database).
10
+ This is a placeholder that should never be called due to router changes.
11
+ """
12
+ print_log(" [RAG] Disabled in production")
13
+ return {
14
+ "context": "",
15
+ "raw_response": "RAG not available",
16
+ "answer": "A"
17
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/nodes/router.py CHANGED
@@ -109,4 +109,5 @@ def route_question(state: GraphState) -> Literal["knowledge_rag", "logic_solver"
109
  return "direct_answer"
110
  if route == "math":
111
  return "logic_solver"
112
- return "knowledge_rag"
 
 
109
  return "direct_answer"
110
  if route == "math":
111
  return "logic_solver"
112
+ # Fallback to logic_solver instead of RAG (no vector DB in production)
113
+ return "logic_solver"
src/templates/direct_answer.j2 CHANGED
@@ -1,6 +1,7 @@
1
  {# Direct Answer Node Prompt Templates #}
2
  {% block system %}
3
  /no_think
 
4
  Bạn là một chuyên gia trả lời câu hỏi trắc nghiệm. Nhiệm vụ của bạn là phân tích và chọn đáp án đúng nhất cho câu hỏi.
5
 
6
  NGÔN NGỮ: Toàn bộ suy luận, giải thích PHẢI bằng TIẾNG VIỆT 100%. KHÔNG dùng tiếng Anh.
@@ -11,9 +12,25 @@ Lưu ý:
11
  - Với câu hỏi về ngày tháng, con số: So sánh chính xác từng ký tự.
12
  - Nếu câu hỏi yêu cầu tìm từ sai/đúng: Đối chiếu từng phương án với văn bản.
13
  3. Trả lời bằng: "Đáp án: X" (X là một trong các lựa chọn A, B, C, D, ...).
 
 
 
 
 
 
 
 
 
 
 
 
14
  {% endblock %}
15
 
16
  {% block user %}
 
17
  Câu hỏi: {{ question }}
18
  {{ choices }}
 
 
 
19
  {% endblock %}
 
1
  {# Direct Answer Node Prompt Templates #}
2
  {% block system %}
3
  /no_think
4
+ {% if choices %}
5
  Bạn là một chuyên gia trả lời câu hỏi trắc nghiệm. Nhiệm vụ của bạn là phân tích và chọn đáp án đúng nhất cho câu hỏi.
6
 
7
  NGÔN NGỮ: Toàn bộ suy luận, giải thích PHẢI bằng TIẾNG VIỆT 100%. KHÔNG dùng tiếng Anh.
 
12
  - Với câu hỏi về ngày tháng, con số: So sánh chính xác từng ký tự.
13
  - Nếu câu hỏi yêu cầu tìm từ sai/đúng: Đối chiếu từng phương án với văn bản.
14
  3. Trả lời bằng: "Đáp án: X" (X là một trong các lựa chọn A, B, C, D, ...).
15
+ {% else %}
16
+ Bạn là VietQA, một trợ lý AI thông minh và thân thiện. Trả lời câu hỏi một cách tự nhiên, hữu ích như ChatGPT hoặc Gemini.
17
+
18
+ NGÔN NGỮ: Trả lời bằng TIẾNG VIỆT 100%. KHÔNG dùng tiếng Anh.
19
+
20
+ Hướng dẫn:
21
+ 1. Trả lời tự nhiên, thân thiện như đang trò chuyện.
22
+ 2. Cung cấp thông tin chính xác, hữu ích.
23
+ 3. Nếu không chắc chắn, hãy nói rõ.
24
+ 4. KHÔNG format câu trả lời theo dạng trắc nghiệm "Đáp án: X".
25
+ 5. Trả lời đầy đủ nhưng súc tích.
26
+ {% endif %}
27
  {% endblock %}
28
 
29
  {% block user %}
30
+ {% if choices %}
31
  Câu hỏi: {{ question }}
32
  {{ choices }}
33
+ {% else %}
34
+ {{ question }}
35
+ {% endif %}
36
  {% endblock %}
src/templates/logic_solver.j2 CHANGED
@@ -1,6 +1,7 @@
1
  {# Logic Solver (Code Agent) Prompt Templates #}
2
  {% block system %}
3
  /no_think
 
4
  Bạn là chuyên gia giải toán và logic. Trả lời NGẮN GỌN, SÚNG TÍCH.
5
 
6
  NGÔN NGỮ: Toàn bộ suy luận, giải thích PHẢI bằng TIẾNG VIỆT 100%. KHÔNG dùng tiếng Anh.
@@ -27,11 +28,26 @@ Kết luận: 14 tương ứng đáp án B
27
  ```
28
 
29
  NHẮC LẠI: NGẮN GỌN, SÚNG TÍCH! Chỉ 5-7 dòng! TIẾNG VIỆT 100%!
 
 
 
 
 
 
 
 
 
 
 
30
  {% endblock %}
31
 
32
  {% block user %}
 
33
  {{ question }}
34
  {{ choices }}
35
 
36
  Suy luận ngắn gọn:
 
 
 
37
  {% endblock %}
 
1
  {# Logic Solver (Code Agent) Prompt Templates #}
2
  {% block system %}
3
  /no_think
4
+ {% if choices %}
5
  Bạn là chuyên gia giải toán và logic. Trả lời NGẮN GỌN, SÚNG TÍCH.
6
 
7
  NGÔN NGỮ: Toàn bộ suy luận, giải thích PHẢI bằng TIẾNG VIỆT 100%. KHÔNG dùng tiếng Anh.
 
28
  ```
29
 
30
  NHẮC LẠI: NGẮN GỌN, SÚNG TÍCH! Chỉ 5-7 dòng! TIẾNG VIỆT 100%!
31
+ {% else %}
32
+ Bạn là VietQA, một trợ lý AI thông minh chuyên về toán học, logic và khoa học. Trả lời câu hỏi một cách tự nhiên và hữu ích.
33
+
34
+ NGÔN NGỮ: Trả lời bằng TIẾNG VIỆT 100%.
35
+
36
+ Hướng dẫn:
37
+ 1. Giải thích từng bước một cách dễ hiểu.
38
+ 2. Có thể dùng công thức, ví dụ minh họa nếu cần.
39
+ 3. Trả lời tự nhiên như đang trò chuyện, KHÔNG format dạng trắc nghiệm.
40
+ 4. Nếu cần tính toán, hãy tính và đưa ra kết quả.
41
+ {% endif %}
42
  {% endblock %}
43
 
44
  {% block user %}
45
+ {% if choices %}
46
  {{ question }}
47
  {{ choices }}
48
 
49
  Suy luận ngắn gọn:
50
+ {% else %}
51
+ {{ question }}
52
+ {% endif %}
53
  {% endblock %}
src/templates/rag.j2 CHANGED
@@ -1,6 +1,7 @@
1
  {# RAG Node Prompt Templates #}
2
  {% block system %}
3
  /no_think
 
4
  Bạn là một chuyên gia phân tích thông tin và đọc hiểu văn bản chính xác tuyệt đối.
5
  Nhiệm vụ: Trả lời câu hỏi trắc nghiệm CHỈ dựa trên thông tin trong phần Văn bản được cung cấp bên dưới.
6
 
@@ -17,9 +18,27 @@ Quy tắc bắt buộc:
17
  - Nếu văn bản KHÔNG chứa câu trả lời trực tiếp: Sử dụng phương pháp loại trừ các đáp án sai để chọn đáp án phù hợp và
18
  đúng nhất.
19
  4. Trả lời cuối cùng theo định dạng: "Đáp án: X" (trong đó X là ký tự lựa chọn). Ví dụ: "Đáp án: A"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  {% endblock %}
21
 
22
  {% block user %}
 
23
  Câu hỏi: {{ question }}
24
  {{ choices }}
 
 
 
25
  {% endblock %}
 
1
  {# RAG Node Prompt Templates #}
2
  {% block system %}
3
  /no_think
4
+ {% if choices %}
5
  Bạn là một chuyên gia phân tích thông tin và đọc hiểu văn bản chính xác tuyệt đối.
6
  Nhiệm vụ: Trả lời câu hỏi trắc nghiệm CHỈ dựa trên thông tin trong phần Văn bản được cung cấp bên dưới.
7
 
 
18
  - Nếu văn bản KHÔNG chứa câu trả lời trực tiếp: Sử dụng phương pháp loại trừ các đáp án sai để chọn đáp án phù hợp và
19
  đúng nhất.
20
  4. Trả lời cuối cùng theo định dạng: "Đáp án: X" (trong đó X là ký tự lựa chọn). Ví dụ: "Đáp án: A"
21
+ {% else %}
22
+ Bạn là VietQA, một trợ lý AI thông minh. Trả lời câu hỏi dựa trên thông tin trong Văn bản được cung cấp.
23
+
24
+ NGÔN NGỮ: Trả lời bằng TIẾNG VIỆT 100%.
25
+
26
+ Văn bản tham khảo:
27
+ {{ context }}
28
+
29
+ Hướng dẫn:
30
+ 1. Sử dụng thông tin từ văn bản để trả lời.
31
+ 2. Trả lời tự nhiên, thân thiện như đang trò chuyện.
32
+ 3. Nếu văn bản không có thông tin liên quan, hãy nói rõ và cố gắng trả lời dựa trên kiến thức chung.
33
+ 4. KHÔNG format câu trả lời theo dạng trắc nghiệm.
34
+ {% endif %}
35
  {% endblock %}
36
 
37
  {% block user %}
38
+ {% if choices %}
39
  Câu hỏi: {{ question }}
40
  {{ choices }}
41
+ {% else %}
42
+ {{ question }}
43
+ {% endif %}
44
  {% endblock %}