-
Notifications
You must be signed in to change notification settings - Fork 0
feat(ai): Single-turn Router → Supervisor 패턴 전환 (#51) #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2d05761
7fc800c
51e983b
779e522
d9c0e03
0f5d729
dba1fa0
27edbf8
6e8dc1a
1dc0c4b
7e7d8ae
cdf90d2
3d9d3fe
a64dfe0
2374e1b
5e308a8
fed3767
5de1a26
91ccb72
9dfdc3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,10 +77,11 @@ def _similarity_search_legal_documents_pgvector( | |
| connect_timeout=self.connect_timeout_seconds, | ||
| ) as conn: | ||
| with conn.cursor() as cursor: | ||
| cursor.execute( | ||
| "set local statement_timeout = %s", | ||
| (self.statement_timeout_ms,), | ||
| ) | ||
| cursor.execute(f"SET LOCAL statement_timeout = {int(self.statement_timeout_ms)}") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor 🧩 Analysis chain🏁 Script executed: #!/bin/bash
fd test_legal_retriever.py backend-ai/tests --exec sed -n '120,185p' {}
fd supabase_client.py backend-ai/app/clients --exec sed -n '74,90p' {}Repository: ssafy-salman/salmanhae Length of output: 3102 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Search for all execute() test doubles and related expectations in backend-ai tests
rg -n "def execute\(self, sql|execute\(sql, params\)|SET LOCAL statement_timeout|statement_timeout" backend-ai/tests
# Show the full affected test file for context around the existing assertion
sed -n '1,260p' backend-ai/tests/test_legal_retriever.py
# Show the current client implementation around the timeout calls
sed -n '70,95p' backend-ai/app/clients/supabase_client.pyRepository: ssafy-salman/salmanhae Length of output: 10386 Update the timeout test expectations. 🧰 Tools🪛 OpenGrep (1.23.0)[ERROR] 80-80: SQL query built via f-string passed to execute()/executemany(). Use parameterized queries with placeholders instead. (coderabbit.sql-injection.python-fstring-execute) 🤖 Prompt for AI Agents |
||
| # IVFFlat 인덱스가 lists=100으로 설정돼 있으나 데이터 수가 적을 때 | ||
| # 기본 probes=1이면 대부분의 클러스터를 건너뛰어 결과가 0개가 됨. | ||
| # probes를 lists 값과 동일하게 설정해 전체 인덱스를 탐색하도록 한다. | ||
| cursor.execute("SET LOCAL ivfflat.probes = 100") | ||
| cursor.execute(sql, (vector_literal, vector_literal, top_k)) | ||
| return list(cursor.fetchall()) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,70 +1,54 @@ | ||
| from langgraph.graph import END, START, StateGraph | ||
|
|
||
| from app.graph.nodes.classify_intent import classify_intent | ||
| from app.graph.nodes.general_chat import general_chat | ||
| from app.graph.nodes.generate_answer import generate_answer | ||
| from app.graph.nodes.legal_rag import legal_rag | ||
| from app.graph.nodes.price_analysis import price_analysis | ||
| from app.graph.nodes.property_search import property_search | ||
| from app.graph.nodes.safety_analysis import safety_analysis | ||
| from app.graph.state import AgentState, Intent | ||
|
|
||
|
|
||
| def route_by_intent(state: AgentState) -> str: | ||
| intent = state.get("intent", Intent.FALLBACK) | ||
| return { | ||
| Intent.PROPERTY_SEARCH: "property_search", | ||
| Intent.LEGAL_CONSULT: "legal_rag", | ||
| Intent.PRICE_ANALYSIS: "price_analysis", | ||
| Intent.SAFETY_ANALYSIS: "safety_analysis", | ||
| Intent.HUG_CALC: "fallback", | ||
| Intent.GENERAL_CHAT: "fallback", | ||
| Intent.FALLBACK: "fallback", | ||
| }[intent] | ||
|
|
||
|
|
||
| def fallback(state: AgentState) -> AgentState: | ||
| next_actions = state.get("next_actions", []) | ||
| next_actions.append( | ||
| { | ||
| "type": "ASK_CLARIFYING_QUESTION", | ||
| "label": "질문 구체화", | ||
| } | ||
| ) | ||
| return { | ||
| **state, | ||
| "tool_results": { | ||
| **state.get("tool_results", {}), | ||
| "fallback": {"reason": "No MVP tool is available for this intent yet."}, | ||
| }, | ||
| "next_actions": next_actions, | ||
| from app.graph.nodes.supervisor import supervisor | ||
| from app.graph.state import AgentState | ||
|
|
||
|
|
||
| def route_after_supervisor(state: AgentState) -> str: | ||
| mapping = { | ||
| "PROPERTY_SEARCH": "property_search", | ||
| "LEGAL_CONSULT": "legal_rag", | ||
| "PRICE_ANALYSIS": "price_analysis", | ||
| "SAFETY_ANALYSIS": "safety_analysis", | ||
| "GENERAL_CHAT": "general_chat", | ||
| "FINISH": "generate_answer", | ||
| } | ||
| return mapping.get(state.get("next_worker", "FINISH"), "generate_answer") | ||
|
|
||
|
|
||
| def build_agent_graph(): | ||
| workflow = StateGraph(AgentState) | ||
| workflow.add_node("classify_intent", classify_intent) | ||
|
|
||
| workflow.add_node("supervisor", supervisor) | ||
| workflow.add_node("property_search", property_search) | ||
| workflow.add_node("legal_rag", legal_rag) | ||
| workflow.add_node("price_analysis", price_analysis) | ||
| workflow.add_node("safety_analysis", safety_analysis) | ||
| workflow.add_node("fallback", fallback) | ||
| workflow.add_node("general_chat", general_chat) | ||
| workflow.add_node("generate_answer", generate_answer) | ||
|
|
||
| workflow.add_edge(START, "classify_intent") | ||
| workflow.add_edge(START, "supervisor") | ||
| workflow.add_conditional_edges( | ||
| "classify_intent", | ||
| route_by_intent, | ||
| "supervisor", | ||
| route_after_supervisor, | ||
| { | ||
| "property_search": "property_search", | ||
| "legal_rag": "legal_rag", | ||
| "price_analysis": "price_analysis", | ||
| "safety_analysis": "safety_analysis", | ||
| "fallback": "fallback", | ||
| "general_chat": "general_chat", | ||
| "generate_answer": "generate_answer", | ||
| }, | ||
| ) | ||
|
|
||
| for node_name in ["property_search", "legal_rag", "price_analysis", "safety_analysis", "fallback"]: | ||
| workflow.add_edge(node_name, "generate_answer") | ||
| for node in ["property_search", "legal_rag", "price_analysis", "safety_analysis", "general_chat"]: | ||
| workflow.add_edge(node, "supervisor") | ||
|
|
||
| workflow.add_edge("generate_answer", END) | ||
| return workflow.compile() |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| from app.graph.state import AgentState | ||
|
|
||
|
|
||
| def general_chat(state: AgentState) -> AgentState: | ||
| return state |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import logging | ||
|
|
||
| from app.clients.llm_client import LLMClient | ||
| from app.graph.state import AgentState | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def supervisor(state: AgentState) -> AgentState: | ||
| workers_called = state.get("workers_called", []) | ||
| call_no = len(workers_called) + 1 | ||
| logger.info("[supervisor #%d] 호출됨 | workers_called=%s", call_no, workers_called) | ||
|
|
||
| next_worker = LLMClient().decide_next_worker( | ||
| message=state["message"], | ||
| workers_called=workers_called, | ||
| ) | ||
| logger.info("[supervisor #%d] → next_worker=%s", call_no, next_worker) | ||
|
|
||
| if next_worker != "FINISH": | ||
| workers_called = [*workers_called, next_worker] | ||
| return {**state, "next_worker": next_worker, "workers_called": workers_called} |
Uh oh!
There was an error while loading. Please reload this page.