Skip to content

Commit 7175fa2

Browse files
committed
feat: manager_agent 추가
1 parent becdca2 commit 7175fa2

File tree

4 files changed

+141
-16
lines changed

4 files changed

+141
-16
lines changed

llm_utils/agent.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from langchain_core.messages import AIMessage
2+
from langchain_core.output_parsers import JsonOutputParser
3+
from langchain_core.messages import SystemMessage
4+
from .state import QueryMakerState
5+
from .llm_factory import get_llm
6+
from prompt.template_loader import get_prompt_template
7+
8+
llm = get_llm()
9+
10+
# JSON 스키마 정의
11+
main_agent_schema = {
12+
"type": "object",
13+
"properties": {
14+
"intent": {
15+
"type": "string",
16+
"description": "유저의 의도 (search, end 등)"
17+
},
18+
"user_input": {
19+
"type": "string",
20+
"description": "유저의 입력"
21+
},
22+
"intent_reason": {
23+
"type": "string",
24+
"description": "유저의 의도 파악 이유"
25+
},
26+
},
27+
"required": ["intent", "user_input"]
28+
}
29+
main_agent_parser = JsonOutputParser(schema=main_agent_schema)
30+
31+
32+
33+
def manager_agent(state: QueryMakerState) -> dict:
34+
"""
35+
가장 처음 시작하는 agent로 질문의 유무를 판단해서 적절한 Agent를 호출합니다.
36+
추후, 가드레일 등에 detecting될 경우에도 해당 노드를 통해 대응이 가능합니다
37+
"""
38+
manager_agent_prompt = get_prompt_template("manager_agent_prompt")
39+
messages = [SystemMessage(content=manager_agent_prompt), state["messages"][-1]]
40+
response = llm.invoke(messages)
41+
42+
try:
43+
parsed_output = main_agent_parser.parse(response.content)
44+
state.update({
45+
"messages": state["messages"] + [response], # 기록용
46+
"intent": parsed_output.get("intent", "end"), # 분기용
47+
"user_input": parsed_output.get("user_input", ""), # SQL 쿼리 변환 대상 질문
48+
"intent_reason": parsed_output.get("intent_reason", "") # 분기 이유
49+
})
50+
return state
51+
52+
except Exception as e:
53+
print(f"<<error main-agent: {e} >>")
54+
state.update({
55+
"messages": state["messages"] + [AIMessage(content=response.content)],
56+
"intent": "end",
57+
"intent_reason": response.content
58+
})
59+
return state
60+
61+
62+
def manager_agent_edge(state: QueryMakerState) -> str:
63+
"""
64+
Condition for main_agent
65+
"""
66+
print("=== In condition: main_edge ===")
67+
if state.get("intent") == "make_query":
68+
return "make_query"
69+
else:
70+
return "end" # end 시 최종 출력 값 반환
71+

llm_utils/graph.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,43 @@
66
from langgraph.graph.message import add_messages
77
from langchain.chains.sql_database.prompt import SQL_PROMPTS
88
from pydantic import BaseModel, Field
9+
from .agent import manager_agent, manager_agent_edge
910
from .llm_factory import get_llm
10-
1111
from llm_utils.chains import (
1212
query_refiner_chain,
1313
query_maker_chain,
1414
)
15-
16-
from llm_utils.tools import get_info_from_db
1715
from llm_utils.retrieval import search_tables
16+
from langchain.schema import AIMessage
17+
from .state import QueryMakerState
1818

1919
# 노드 식별자 정의
2020
QUERY_REFINER = "query_refiner"
2121
GET_TABLE_INFO = "get_table_info"
2222
TOOL = "tool"
2323
TABLE_FILTER = "table_filter"
2424
QUERY_MAKER = "query_maker"
25+
MANAGER_AGENT = "manager_agent"
26+
EXCEPTION_END_NODE = "exception_end_node"
2527

2628

27-
# 상태 타입 정의 (추가 상태 정보와 메시지들을 포함)
28-
class QueryMakerState(TypedDict):
29-
messages: Annotated[list, add_messages]
30-
user_database_env: str
31-
searched_tables: dict[str, dict[str, str]]
32-
best_practice_query: str
33-
refined_input: str
34-
generated_query: str
35-
retriever_name: str
36-
top_n: int
37-
device: str
29+
def exception_end_node(state: QueryMakerState):
30+
intent_reason = state.get("intent_reason", "SQL 쿼리 생성을 위한 질문을 해주세요")
31+
end_message_prompt = f"""
32+
다음과 같은 이유로 답변을 할 수 없습니다!
33+
```
34+
{intent_reason}
35+
```
36+
"""
37+
return {
38+
"messages": state["messages"] + [AIMessage(content=end_message_prompt)],
39+
}
40+
3841

3942

4043
# 노드 함수: QUERY_REFINER 노드
4144
def query_refiner_node(state: QueryMakerState):
45+
# refined_node의 결과값으로 바로 AIMessages 반환
4246
res = query_refiner_chain.invoke(
4347
input={
4448
"user_input": [state["messages"][0].content],
@@ -67,6 +71,7 @@ def get_table_info_node(state: QueryMakerState):
6771

6872
# 노드 함수: QUERY_MAKER 노드
6973
def query_maker_node(state: QueryMakerState):
74+
# sturctured output 사용
7075
res = query_maker_chain.invoke(
7176
input={
7277
"user_input": [state["messages"][0].content],
@@ -105,19 +110,33 @@ def query_maker_node_with_db_guide(state: QueryMakerState):
105110

106111
# StateGraph 생성 및 구성
107112
builder = StateGraph(QueryMakerState)
108-
builder.set_entry_point(GET_TABLE_INFO)
109-
110113
# 노드 추가
114+
builder.add_node(MANAGER_AGENT, manager_agent)
111115
builder.add_node(GET_TABLE_INFO, get_table_info_node)
112116
builder.add_node(QUERY_REFINER, query_refiner_node)
113117
builder.add_node(QUERY_MAKER, query_maker_node) # query_maker_node_with_db_guide
118+
builder.add_node(EXCEPTION_END_NODE, exception_end_node)
114119
# builder.add_node(
115120
# QUERY_MAKER, query_maker_node_with_db_guide
116121
# ) # query_maker_node_with_db_guide
117122

118123
# 기본 엣지 설정
124+
builder.set_entry_point(MANAGER_AGENT)
119125
builder.add_edge(GET_TABLE_INFO, QUERY_REFINER)
120126
builder.add_edge(QUERY_REFINER, QUERY_MAKER)
121127

128+
# 조건부 엣지
129+
builder.add_conditional_edges(
130+
MANAGER_AGENT,
131+
manager_agent_edge,
132+
{
133+
"end": EXCEPTION_END_NODE,
134+
"make_query": GET_TABLE_INFO,
135+
},
136+
)
137+
122138
# QUERY_MAKER 노드 후 종료
123139
builder.add_edge(QUERY_MAKER, END)
140+
141+
# EXCEPTION_END_NODE 노드 후 종료
142+
builder.add_edge(EXCEPTION_END_NODE, END)

llm_utils/state.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from typing_extensions import TypedDict, Annotated
2+
from langgraph.graph.message import add_messages
3+
4+
5+
# 상태 타입 정의 (추가 상태 정보와 메시지들을 포함)
6+
class QueryMakerState(TypedDict):
7+
messages: Annotated[list, add_messages]
8+
user_database_env: str
9+
searched_tables: dict[str, dict[str, str]]
10+
best_practice_query: str
11+
refined_input: str
12+
generated_query: str
13+
retriever_name: str
14+
top_n: int
15+
device: str

prompt/manager_agent_prompt.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Role
2+
3+
사용자의 질문을 기반으로, 주어진 테이블과 컬럼 정보를 활용하여 적절한 SQL 쿼리를 생성하는 데 도움을 주는 Agent입니다.
4+
사용자의 입력을 분석하여 다음 중에서 의도(intent)를 파악하고, 만약 SQL 쿼리나 주어진 DB에 관련된 질문이 아닐 경우, end node를 반환합니다.
5+
6+
1. end: SQL 관련 요청이 아닐 경우 (예: "오늘 날씨가 어때?", "Postgres에 대해 알려줘")
7+
2. make_query: DB검색 관련 요청
8+
9+
agent는 key로 관리되며 다음과 같은 키가 있습니다.
10+
intent가 make_query일 경우, 아래와 같은 형태로 결과를 반환해주세요.
11+
응답 형식:
12+
{
13+
"intent": "<의도>",
14+
"user_input": "<사용자의 입력>",
15+
"intent_reason": "<의도 파악 이유>"
16+
}
17+
# 주의사항
18+
- 서술형 대답이 필요한 경우, Intent에 대한 reason만 입력해주세요.
19+
- Intent Reason은 최대한 쉽고 간단하게 입력하되, 존댓말을 쓰세요.
20+
- Json 형식으로만 응답을 해야하며, 추가적인 정보나 문구를 입력하지 마세요.

0 commit comments

Comments
 (0)