peekabook-api / app /tools /api_tools_v2.py
lael
feat: update to graph_main with HyDE RAG, user_id isolation, GPU auto-detect
7d2a564
Raw
History Blame Contribute Delete
3.31 kB
import os
from dotenv import load_dotenv
from langchain_core.messages import AIMessage, HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from app.state.state import GraphState
from app.tools.tools import check_book_availability_in_district
load_dotenv()
os.environ.setdefault("OPENAI_API_KEY", os.getenv("OPENAI_API_KEY", ""))
tools = [check_book_availability_in_district]
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
system_prompt = """당신은 λ„μ„œκ΄€ μ±… μΆ”μ²œ νλ ˆμ΄ν„°μž…λ‹ˆλ‹€.
[절차]
1. μΆ”μ²œ λ„μ„œ 3ꢌ 각각에 λŒ€ν•΄ check_book_availability_in_district 도ꡬλ₯Ό ν˜ΈμΆœν•˜μ„Έμš”.
- district_name: μ‚¬μš©μž μ§€μ—­ ꡬ 이름 (예: 강남ꡬ)
- isbn13: 각 λ„μ„œμ˜ ISBN
2. 도ꡬ κ²°κ³Όλ₯Ό λ°”νƒ•μœΌλ‘œ μ•„λž˜ 좜λ ₯ ν˜•μ‹μ— 맞게 닡변을 μž‘μ„±ν•˜μ„Έμš”.
[좜λ ₯ ν˜•μ‹ - λ°˜λ“œμ‹œ 지킬 것]
μΆ”μ²œ λ„μ„œλ§ˆλ‹€ μ•„λž˜ ν˜•μ‹μœΌλ‘œ μž‘μ„±ν•˜μ„Έμš”.
---
πŸ“š {μ±… 제λͺ©} | {μ €μž}
![μ±… 제λͺ©](ν‘œμ§€URL)
πŸ“– μ±… μ†Œκ°œ
(전달받은 μ±…μ†Œκ°œλ₯Ό κ·ΈλŒ€λ‘œ μž‘μ„±)
✏️ μΆ”μ²œ 이유
(전달받은 μΆ”μ²œ 이유λ₯Ό κ·ΈλŒ€λ‘œ μž‘μ„±)
πŸ“ λŒ€μΆœ κ°€λŠ₯ μ—¬λΆ€
- (λ„μ„œκ΄€ 이름): (λŒ€μΆœ κ°€λŠ₯) or (λŒ€μΆœ 쀑) or (λ―Έμ†Œμž₯) or (확인 λΆˆκ°€)
---
[μ£Όμ˜μ‚¬ν•­]
- μΆ”μ²œ λ„μ„œ λͺ©λ‘μ˜ 3κΆŒμ„ λ°˜λ“œμ‹œ λͺ¨λ‘ μœ„ ν˜•μ‹μœΌλ‘œ 좜λ ₯ν•˜μ„Έμš”. μ˜ˆμ™ΈλŠ” μ—†μŠ΅λ‹ˆλ‹€.
- λŒ€μΆœ κ°€λŠ₯ 여뢀와 관계없이 3ꢌ μ „λΆ€ 좜λ ₯ν•˜μ„Έμš”.
- ν‘œμ§€ μ΄λ―Έμ§€λŠ” [μΆ”μ²œ λ„μ„œ] λͺ©λ‘μ— 제곡된 cover_url을 κ·ΈλŒ€λ‘œ μ‚¬μš©ν•˜μ„Έμš”. λ„κ΅¬λ‘œ κ°€μ Έμ˜€μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
- cover_url이 λΉ„μ–΄ 있으면 이미지 라인은 μƒλž΅ν•˜μ„Έμš”.
- 책을 μ œκ±°ν•˜κ±°λ‚˜ λ‹€λ₯Έ μ±…μœΌλ‘œ λŒ€μ²΄ν•˜λŠ” 것은 μ ˆλŒ€ κΈˆμ§€μž…λ‹ˆλ‹€.
- λŒ€μΆœ μ •λ³΄λŠ” λ°˜λ“œμ‹œ 도ꡬ 호좜 결과만 μ‚¬μš©ν•˜μ„Έμš”. μ ˆλŒ€ μ§€μ–΄λ‚΄μ§€ λ§ˆμ„Έμš”.
"""
agent_executor = create_react_agent(llm, tools, prompt=system_prompt)
def api_tool_calling_node(state: GraphState) -> dict:
recommendations = state.get("recommendations", [])
summary = state.get("summary", "")
district = "쀑ꡬ"
if not recommendations:
msg = "κ²€μƒ‰λœ λ„μ„œκ°€ μ—†μ–΄ μΆ”μ²œμ„ μ œκ³΅ν•  수 μ—†μŠ΅λ‹ˆλ‹€."
return {"messages": [AIMessage(content=msg)]}
# retrieved_booksμ—μ„œ ISBN κΈ°μ€€μœΌλ‘œ 원본 book_intro 쑰회 (rag_llm_node의 300자 잘림 우회)
retrieved_index = {b.get("isbn", ""): b for b in state.get("retrieved_books", [])}
if isinstance(recommendations, str):
rec_text = recommendations
else:
rec_text = "\n".join([
f"- 제λͺ©: {r['title']}, μ €μž: {r['author']}, ISBN: {r['isbn']}, cover_url: {r.get('cover_url', '')}, "
f"μ±…μ†Œκ°œ: {retrieved_index.get(r['isbn'], {}).get('book_intro', r.get('book_intro', ''))}, "
f"μΆ”μ²œ 이유: {r['reason']}"
for r in recommendations
])
query = f"""
μ•„λž˜ μΆ”μ²œ λ„μ„œ 3ꢌ의 {district} λ„μ„œκ΄€ λŒ€μΆœ κ°€λŠ₯ μ—¬λΆ€λ₯Ό ν™•μΈν•΄μ„œ μ΅œμ’… μΆ”μ²œ 닡변을 λ§Œλ“€μ–΄μ€˜.
ν‘œμ§€ μ΄λ―Έμ§€λŠ” 각 λ„μ„œμ˜ cover_url을 κ·ΈλŒ€λ‘œ μ‚¬μš©ν•΄.
[μΆ”μ²œ λ„μ„œ]
{rec_text}
[μ‚¬μš©μž ν”„λ‘œνŒŒμΌ]
{summary}
"""
result = agent_executor.invoke({"messages": [HumanMessage(content=query)]})
return {"messages": [result["messages"][-1]]}