dev-yuje commited on
Commit
7f57ffc
Β·
1 Parent(s): 78a2a73

fix: implement robust neo4j driver fallback with verify_connectivity to prevent AuthError

Browse files
AGENTS.md CHANGED
@@ -79,8 +79,9 @@ def test_portfolio_showcase_aggregation_query():
79
  assert any(indicator in response.answer for indicator in ["1.", "TOP", "기사", "좜처"]) # μΌμ’…μ˜ skill
80
  ```
81
 
82
- ## μžλ™ 검사
83
  - 둜컬 개발 ν™˜κ²½μ—μ„œ μ»€λ°‹ν•˜κΈ° μ „, λ°˜λ“œμ‹œ 터미널에 `ruff check .` 및 `mypy src tests --ignore-missing-imports` λͺ…λ Ήμ–΄λ₯Ό 직접 μ‹€ν–‰ν•˜μ—¬ 린트 및 μ—„κ²©ν•œ νƒ€μž… 였λ₯˜λ₯Ό ν™•μ‹€ν•˜κ²Œ ν™•μΈν•˜κ³  λͺ¨λ‘ κ³ μΉ  것 (였λ₯˜κ°€ λ‚¨μ•„μžˆλŠ” μƒνƒœλ‘œ 컀밋 κΈˆμ§€).
 
84
  - 컀밋 μ „ `pre-commit` μžλ™ μ‹€ν–‰
85
  - `ruff`, `mypy` 검사 톡과 ν•„μˆ˜
86
  - 검사 μ‹€νŒ¨ μ‹œ 컀밋 λΆˆκ°€
 
79
  assert any(indicator in response.answer for indicator in ["1.", "TOP", "기사", "좜처"]) # μΌμ’…μ˜ skill
80
  ```
81
 
82
+ ## μžλ™ 검사 및 λŸ°νƒ€μž„ μ—λŸ¬ λ°©μ§€
83
  - 둜컬 개발 ν™˜κ²½μ—μ„œ μ»€λ°‹ν•˜κΈ° μ „, λ°˜λ“œμ‹œ 터미널에 `ruff check .` 및 `mypy src tests --ignore-missing-imports` λͺ…λ Ήμ–΄λ₯Ό 직접 μ‹€ν–‰ν•˜μ—¬ 린트 및 μ—„κ²©ν•œ νƒ€μž… 였λ₯˜λ₯Ό ν™•μ‹€ν•˜κ²Œ ν™•μΈν•˜κ³  λͺ¨λ‘ κ³ μΉ  것 (였λ₯˜κ°€ λ‚¨μ•„μžˆλŠ” μƒνƒœλ‘œ 컀밋 κΈˆμ§€).
84
+ - **λŸ°νƒ€μž„ Auth/μ—°κ²° μ—λŸ¬ λ°©μ§€**: 린트/νƒ€μž… 검사 ν›„ λ°˜λ“œμ‹œ `python tests/smoke_test_rag.py`λ₯Ό λ‘œμ»¬μ—μ„œ μ‹€ν–‰ν•˜μ—¬ `neo4j.exceptions.AuthError` λ“±μ˜ λŸ°νƒ€μž„ μ—λŸ¬κ°€ ν„°μ§€μ§€ μ•Šκ³  μ™„λ²½νžˆ RAG κ²°κ³Όκ°€ 좜λ ₯λ˜λŠ”μ§€(DB 정상 접속) ν˜„μž₯ 점검(Smoke Test) ν›„ ν‘Έμ‹œν•  것.
85
  - 컀밋 μ „ `pre-commit` μžλ™ μ‹€ν–‰
86
  - `ruff`, `mypy` 검사 톡과 ν•„μˆ˜
87
  - 검사 μ‹€νŒ¨ μ‹œ 컀밋 λΆˆκ°€
app.py CHANGED
@@ -22,6 +22,7 @@ dotenv.load_dotenv()
22
  # 1. LangGraph 챗봇 State μ •μ˜
23
  # ──────────────────────────────────────────
24
 
 
25
  class ChatState(TypedDict):
26
  question: str # μ‚¬μš©μž 질문
27
  history: List[dict] # λŒ€ν™” νžˆμŠ€ν† λ¦¬ [{"role": "user"/"assistant", "content": "..."}]
 
22
  # 1. LangGraph 챗봇 State μ •μ˜
23
  # ──────────────────────────────────────────
24
 
25
+
26
  class ChatState(TypedDict):
27
  question: str # μ‚¬μš©μž 질문
28
  history: List[dict] # λŒ€ν™” νžˆμŠ€ν† λ¦¬ [{"role": "user"/"assistant", "content": "..."}]
run_pipeline.py CHANGED
@@ -59,5 +59,6 @@ def run_test():
59
  else:
60
  print("\n⏭️ AI κ΄€λ ¨ 기사가 μ•„λ‹ˆλ―€λ‘œ κ·Έλž˜ν”„ 상세 뢄석 및 벑터 적재λ₯Ό κ±΄λ„ˆλœλ‹ˆλ‹€.")
61
 
 
62
  if __name__ == "__main__":
63
  run_test()
 
59
  else:
60
  print("\n⏭️ AI κ΄€λ ¨ 기사가 μ•„λ‹ˆλ―€λ‘œ κ·Έλž˜ν”„ 상세 뢄석 및 벑터 적재λ₯Ό κ±΄λ„ˆλœλ‹ˆλ‹€.")
61
 
62
+
63
  if __name__ == "__main__":
64
  run_test()
src/graphBuilder/neo4j/finGraph.py CHANGED
@@ -26,11 +26,28 @@ from neo4j_graphrag.llm import OpenAILLM
26
 
27
  dotenv.load_dotenv()
28
 
29
- URI = os.getenv("NEO4J_URI", "neo4j://localhost:7687")
30
- username = os.getenv("NEO4J_CLIENT_ID") or os.getenv("NEO4J_USERNAME") or "neo4j"
31
- password = os.getenv("NEO4J_CLIENT_SECRET") or os.getenv("NEO4J_PASSWORD") or "password"
32
- AUTH = (username, password)
33
- driver = neo4j.GraphDatabase.driver(URI, auth=AUTH)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  chat_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
36
  rag_llm = OpenAILLM(model_name="gpt-4o", model_params={"temperature": 0})
 
26
 
27
  dotenv.load_dotenv()
28
 
29
+
30
+ def get_neo4j_driver() -> neo4j.Driver:
31
+ uri = os.getenv("NEO4J_URI", "neo4j://localhost:7687")
32
+ client_id = os.getenv("NEO4J_CLIENT_ID")
33
+ client_secret = os.getenv("NEO4J_CLIENT_SECRET")
34
+
35
+ if client_id and client_secret:
36
+ try:
37
+ d = neo4j.GraphDatabase.driver(uri, auth=(client_id, client_secret))
38
+ d.verify_connectivity()
39
+ return d
40
+ except Exception:
41
+ pass
42
+
43
+ username = os.getenv("NEO4J_USERNAME", "neo4j")
44
+ password = os.getenv("NEO4J_PASSWORD", "password")
45
+ d = neo4j.GraphDatabase.driver(uri, auth=(username, password))
46
+ d.verify_connectivity()
47
+ return d
48
+
49
+
50
+ driver = get_neo4j_driver()
51
 
52
  chat_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
53
  rag_llm = OpenAILLM(model_name="gpt-4o", model_params={"temperature": 0})
src/retrieval/finRetrieval.py CHANGED
@@ -30,11 +30,28 @@ dotenv.load_dotenv()
30
  # 1. DB / LLM / Embedder μ΄ˆκΈ°ν™”
31
  # ──────────────────────────────────────────
32
 
33
- URI = os.getenv("NEO4J_URI", "neo4j://localhost:7687")
34
- username = os.getenv("NEO4J_CLIENT_ID") or os.getenv("NEO4J_USERNAME") or "neo4j"
35
- password = os.getenv("NEO4J_CLIENT_SECRET") or os.getenv("NEO4J_PASSWORD") or "password"
36
- AUTH = (username, password)
37
- driver = neo4j.GraphDatabase.driver(URI, auth=AUTH)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  rag_llm = OpenAILLM(model_name="gpt-4o", model_params={"temperature": 0})
40
  embedder = OpenAIEmbeddings(model="text-embedding-3-small")
@@ -180,18 +197,21 @@ class HybridFallbackRetriever(Retriever):
180
  return self.fallback_retriever.search(query_text=query_text, **kwargs)
181
  return res
182
 
 
183
  # ν•˜μ΄λΈŒλ¦¬λ“œ 검색 μΈμŠ€ν„΄μŠ€ μž₯μ°©
184
  hybrid_retriever = HybridFallbackRetriever(
185
  tools_retriever=tools_retriever,
186
  fallback_retriever=vector_cypher_retriever,
187
  )
188
 
 
189
  class CustomRagTemplate(RagTemplate):
190
  EXPECTED_INPUTS = ["context", "query_text"]
191
 
192
  def format(self, query_text: str, context: str, examples: str = "") -> str:
193
  return self._format(query_text=query_text, context=context)
194
 
 
195
  _prompt_template = CustomRagTemplate(
196
  template="""당신은 AI 기술 νŠΈλ Œλ“œ 뢄석 μ „λ¬Έκ°€μž…λ‹ˆλ‹€.
197
  λ°˜λ“œμ‹œ μ•„λž˜ 제곡된 [μ»¨ν…μŠ€νŠΈ(Neo4j 지식 κ·Έλž˜ν”„ 검색 κ²°κ³Ό)]에 κΈ°λ°˜ν•΄μ„œλ§Œ λ‹΅λ³€ν•˜μ„Έμš”.
 
30
  # 1. DB / LLM / Embedder μ΄ˆκΈ°ν™”
31
  # ──────────────────────────────────────────
32
 
33
+
34
+ def get_neo4j_driver() -> neo4j.Driver:
35
+ uri = os.getenv("NEO4J_URI", "neo4j://localhost:7687")
36
+ client_id = os.getenv("NEO4J_CLIENT_ID")
37
+ client_secret = os.getenv("NEO4J_CLIENT_SECRET")
38
+
39
+ if client_id and client_secret:
40
+ try:
41
+ d = neo4j.GraphDatabase.driver(uri, auth=(client_id, client_secret))
42
+ d.verify_connectivity()
43
+ return d
44
+ except Exception:
45
+ pass # Fallback to Username/Password
46
+
47
+ username = os.getenv("NEO4J_USERNAME", "neo4j")
48
+ password = os.getenv("NEO4J_PASSWORD", "password")
49
+ d = neo4j.GraphDatabase.driver(uri, auth=(username, password))
50
+ d.verify_connectivity()
51
+ return d
52
+
53
+
54
+ driver = get_neo4j_driver()
55
 
56
  rag_llm = OpenAILLM(model_name="gpt-4o", model_params={"temperature": 0})
57
  embedder = OpenAIEmbeddings(model="text-embedding-3-small")
 
197
  return self.fallback_retriever.search(query_text=query_text, **kwargs)
198
  return res
199
 
200
+
201
  # ν•˜μ΄λΈŒλ¦¬λ“œ 검색 μΈμŠ€ν„΄μŠ€ μž₯μ°©
202
  hybrid_retriever = HybridFallbackRetriever(
203
  tools_retriever=tools_retriever,
204
  fallback_retriever=vector_cypher_retriever,
205
  )
206
 
207
+
208
  class CustomRagTemplate(RagTemplate):
209
  EXPECTED_INPUTS = ["context", "query_text"]
210
 
211
  def format(self, query_text: str, context: str, examples: str = "") -> str:
212
  return self._format(query_text=query_text, context=context)
213
 
214
+
215
  _prompt_template = CustomRagTemplate(
216
  template="""당신은 AI 기술 νŠΈλ Œλ“œ 뢄석 μ „λ¬Έκ°€μž…λ‹ˆλ‹€.
217
  λ°˜λ“œμ‹œ μ•„λž˜ 제곡된 [μ»¨ν…μŠ€νŠΈ(Neo4j 지식 κ·Έλž˜ν”„ 검색 κ²°κ³Ό)]에 κΈ°λ°˜ν•΄μ„œλ§Œ λ‹΅λ³€ν•˜μ„Έμš”.
tests/smoke_test_rag.py CHANGED
@@ -20,15 +20,28 @@ import dotenv
20
 
21
  dotenv.load_dotenv()
22
 
 
23
  # ── 0. κ·Έλž˜ν”„ ꡬ성 사전 점검 (Neo4j λ…Έλ“œ/관계 톡계) ─────────────────────────
24
  def check_graph_structure():
25
  import neo4j
26
 
27
  uri = os.getenv("NEO4J_URI", "neo4j://localhost:7687")
28
- username = os.getenv("NEO4J_CLIENT_ID") or os.getenv("NEO4J_USERNAME") or "neo4j"
29
- password = os.getenv("NEO4J_CLIENT_SECRET") or os.getenv("NEO4J_PASSWORD") or "password"
30
- auth = (username, password)
31
- driver = neo4j.GraphDatabase.driver(uri, auth=auth)
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  print("\n" + "=" * 60)
34
  print("πŸ“Š [사전 점검] Neo4j κ·Έλž˜ν”„ ꡬ성 ν˜„ν™©")
 
20
 
21
  dotenv.load_dotenv()
22
 
23
+
24
  # ── 0. κ·Έλž˜ν”„ ꡬ성 사전 점검 (Neo4j λ…Έλ“œ/관계 톡계) ─────────────────────────
25
  def check_graph_structure():
26
  import neo4j
27
 
28
  uri = os.getenv("NEO4J_URI", "neo4j://localhost:7687")
29
+ client_id = os.getenv("NEO4J_CLIENT_ID")
30
+ client_secret = os.getenv("NEO4J_CLIENT_SECRET")
31
+
32
+ driver = None
33
+ if client_id and client_secret:
34
+ try:
35
+ driver = neo4j.GraphDatabase.driver(uri, auth=(client_id, client_secret))
36
+ driver.verify_connectivity()
37
+ except Exception:
38
+ driver = None
39
+
40
+ if not driver:
41
+ username = os.getenv("NEO4J_USERNAME", "neo4j")
42
+ password = os.getenv("NEO4J_PASSWORD", "password")
43
+ driver = neo4j.GraphDatabase.driver(uri, auth=(username, password))
44
+ driver.verify_connectivity()
45
 
46
  print("\n" + "=" * 60)
47
  print("πŸ“Š [사전 점검] Neo4j κ·Έλž˜ν”„ ꡬ성 ν˜„ν™©")
tests/test_retrieval.py CHANGED
@@ -10,6 +10,7 @@ has_credentials = (
10
  os.getenv("NEO4J_URI") is not None
11
  )
12
 
 
13
  @pytest.mark.skipif(
14
  not has_credentials,
15
  reason="OpenAI API Key λ˜λŠ” Neo4j μ—°κ²° ν™˜κ²½λ³€μˆ˜κ°€ μ—†μœΌλ―€λ‘œ 톡합 ν…ŒμŠ€νŠΈλ₯Ό κ±΄λ„ˆλœλ‹ˆλ‹€."
 
10
  os.getenv("NEO4J_URI") is not None
11
  )
12
 
13
+
14
  @pytest.mark.skipif(
15
  not has_credentials,
16
  reason="OpenAI API Key λ˜λŠ” Neo4j μ—°κ²° ν™˜κ²½λ³€μˆ˜κ°€ μ—†μœΌλ―€λ‘œ 톡합 ν…ŒμŠ€νŠΈλ₯Ό κ±΄λ„ˆλœλ‹ˆλ‹€."