Nhat Anh commited on
Commit
693d806
·
1 Parent(s): df859f4

Phase 2: state schema and query decomposer node verified

Browse files
src/agents/__init__.py ADDED
File without changes
src/agents/query_decomposer.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/agents/query_decomposer.py
2
+ import os
3
+ import json
4
+ from langgraph.graph import START
5
+ from langchain_google_genai import ChatGoogleGenerativeAI
6
+ from src.agents.state import AgentState
7
+ from src.utils.logger import logger
8
+ from dotenv import load_dotenv
9
+
10
+ load_dotenv()
11
+
12
+ llm = ChatGoogleGenerativeAI(
13
+ model="gemini-2.5-flash",
14
+ google_api_key=os.getenv("GOOGLE_API_KEY"),
15
+ temperature=0
16
+ )
17
+
18
+ DECOMPOSER_PROMPT = """Bạn là một chuyên gia phân tích câu hỏi pháp lý Việt Nam.
19
+
20
+ Nhiệm vụ: phân tích câu hỏi và chia thành các câu hỏi con để tra cứu.
21
+
22
+ Với mỗi câu hỏi con, xác định:
23
+ - "question": nội dung câu hỏi con
24
+ - "source": nguồn cần tra cứu
25
+ + "domestic": chỉ luật Việt Nam
26
+ + "international": chỉ hiệp định quốc tế (EVFTA, CPTPP)
27
+ + "both": cả hai
28
+ - "domain": lĩnh vực
29
+ + "labor": lao động, việc làm, hợp đồng lao động, tiền lương, công đoàn
30
+ + "food_safety": an toàn thực phẩm, vệ sinh thực phẩm, kiểm dịch
31
+
32
+ Trả lời ONLY bằng JSON hợp lệ, không có text nào khác:
33
+ {{
34
+ "sub_questions": [
35
+ {{
36
+ "question": "...",
37
+ "source": "domestic|international|both",
38
+ "domain": "labor|food_safety"
39
+ }}
40
+ ],
41
+ "requires_international": true|false
42
+ }}
43
+
44
+ Câu hỏi: {query}"""
45
+
46
+ def query_decomposer_node(state: AgentState) -> dict:
47
+ query = state["original_query"]
48
+ logger.info(f"Query Decomposer: '{query[:80]}'")
49
+
50
+ try:
51
+ response = llm.invoke(DECOMPOSER_PROMPT.format(query=query))
52
+ raw = response.content.strip()
53
+
54
+ # Strip markdown fences if present
55
+ if "```" in raw:
56
+ raw = raw.split("```")[1]
57
+ if raw.startswith("json"):
58
+ raw = raw[4:]
59
+ raw = raw.strip()
60
+
61
+ parsed = json.loads(raw)
62
+
63
+ sub_questions = parsed["sub_questions"]
64
+ requires_international = parsed["requires_international"]
65
+
66
+ logger.success(f"Decomposed into {len(sub_questions)} sub-question(s)")
67
+ for sq in sub_questions:
68
+ logger.info(f" → [{sq['source']}|{sq['domain']}] {sq['question']}")
69
+
70
+ return {
71
+ "sub_questions": sub_questions,
72
+ "requires_international": requires_international,
73
+ "error": None
74
+ }
75
+
76
+ except Exception as e:
77
+ logger.error(f"Query Decomposer failed: {e}")
78
+ return {
79
+ "sub_questions": [{
80
+ "question": query,
81
+ "source": "domestic",
82
+ "domain": "labor"
83
+ }],
84
+ "requires_international": False,
85
+ "error": str(e)
86
+ }
src/agents/state.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/agents/state.py
2
+ from typing import TypedDict, Optional, Annotated
3
+ import operator
4
+
5
+ class AgentState(TypedDict):
6
+ # Input
7
+ original_query: str
8
+
9
+ # Query decomposition
10
+ sub_questions: list[dict] # each: {question, source, domain}
11
+ requires_international: bool
12
+
13
+ # Retrieval results — store only references, not full text
14
+ domestic_chunks: Annotated[list[dict], operator.add]
15
+ international_chunks: Annotated[list[dict], operator.add]
16
+
17
+ # Cross-reference output
18
+ cross_reference: Optional[dict]
19
+
20
+ # Final output
21
+ final_answer: str
22
+ citations: list[str]
23
+ has_expired_docs: bool
24
+
25
+ # Control
26
+ error: Optional[str]
src/agents/test_decomposer.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/agents/test_decomposer.py
2
+ from src.agents.state import AgentState
3
+ from src.agents.query_decomposer import query_decomposer_node
4
+ from src.utils.logger import logger
5
+
6
+ test_queries = [
7
+ "Hợp đồng lao động phải có những nội dung gì theo quy định hiện hành?",
8
+ "Điều kiện an toàn thực phẩm trong sản xuất thực phẩm xuất khẩu sang EU theo EVFTA là gì?",
9
+ "Việt Nam có đáp ứng các tiêu chuẩn lao động của CPTPP về tự do hiệp hội không?",
10
+ ]
11
+
12
+ for query in test_queries:
13
+ logger.info(f"\n{'='*60}")
14
+ initial_state: AgentState = {
15
+ "original_query": query,
16
+ "sub_questions": [],
17
+ "requires_international": False,
18
+ "domestic_chunks": [],
19
+ "international_chunks": [],
20
+ "cross_reference": None,
21
+ "final_answer": "",
22
+ "citations": [],
23
+ "has_expired_docs": False,
24
+ "error": None
25
+ }
26
+ result = query_decomposer_node(initial_state)
27
+ logger.info(f"requires_international: {result['requires_international']}")
28
+ logger.info(f"sub_questions: {result['sub_questions']}")