
[1] Chat Input
โ (์ฌ์ฉ์ ์ง๋ฌธ ์ ๋ฌ)
[2] MultiQuery Search (Custom Component)
โ (DataFrame ๋ฐํ)
[3] Parser (DataFrame โ ๋ฌธ์์ด context)
โ
[4] Prompt Template (context + question)
โ
[5] OpenAI (LLM)
โ
[6] Chat Output
์ฌ์ฉ์๊ฐ ์ง๋ฌธ์ ์ ๋ ฅํ๋ ์์์
| Input | ์ค๋ช |
|---|---|
| collection_name | Qdrant ์ ์ฅ์ ์ด๋ฆ |
| openai_api_key | embedding์ฉ |
| qdrant_url | Qdrant ์๋ฒ URL |
| user_question | ChatInput์์ ์ ๋ฌ๋ฐ์ ์ง๋ฌธ |
| n_queries | ์์ฑํ ์๋ธ์ฟผ๋ฆฌ ๊ฐ์ |
| top_k | ๊ฐ ์ฟผ๋ฆฌ๋น ๊ฒ์ํ ๋ฌธ์ ๊ฐ์ |
from langflow.custom import Component
from langflow.io import StrInput,SecretStrInput,MessageTextInput,IntInput,Output
from langflow.schema import DataFrame
from langflow.schema.message import Message
from qdrant_client import QdrantClient
from openai import OpenAI
class MultiQuerySearch(Component):
display_name = "MultiQuery Search"
description = "๋ฉํฐ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๊ณ Qdrant์์ ๊ฒ์ ํ DataFrame ํ์์ผ๋ก ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค."
icon = "search"
name = "MultiQuerySearch"
inputs = [StrInput(name="collection_name", display_name="Collection Name", required=True),
SecretStrInput(name="openai_api_key", display_name="OpenAI API Key", required=True),
SecretStrInput(name="qdrant_api_key", display_name="Qdrant API Key", required=False, advanced=True),
StrInput(name="qdrant_url", display_name="Qdrant URL", required=True),
MessageTextInput(name="user_question", display_name="User Question", required=False),
IntInput(name="n_queries", display_name="Number of sub-queries", value=4, required=True),
IntInput(name="top_k", display_name="Top-K per query", value=4, required=True),]
outputs = [Output(name="search_results", display_name="Search Results", method="run_search"),]
def _get_openai_client(self) -> OpenAI:
if not hasattr(self, "_openai_client"):
self._openai_client = OpenAI(api_key=self.openai_api_key)
return self._openai_client
def _get_qdrant_client(self) -> QdrantClient:
if not hasattr(self, "_qdrant_client"):
self._qdrant_client = QdrantClient(url=self.qdrant_url,api_key=self.qdrant_api_key or None)
return self._qdrant_client
def _expand_queries(self, question: str) -> list[str]:
base = question.strip()
variations = [base,
base + " ๊ด๋ จ ๊ฐ์ฌ ์ฌ๋ก",
base + " ์ ์ฌ ํ๊ณ ์ฒ๋ฆฌ ์ฌ๋ก",
base + " ์ ์ฌยท์ฒ๋ถ ์์ ์ฌ๋ก",]
return variations[: max(1, self.n_queries)]
def _embed_text(self, text: str) -> list[float]:
client = self._get_openai_client()
resp = client.embeddings.create(model="text-embedding-3-small", input=text,)
return resp.data[0].embedding
# run_search ์ ์ฒด
def run_search(self) -> DataFrame:
uq = self.user_question
# --- Message ํ์
์ฒ๋ฆฌ ---
if isinstance(uq, Message):
question = (uq.text or "").strip()
else:
question = (uq or "").strip()
# --- LangFlow ์ด๊ธฐ ๋น๋์์๋ user_question์ด None ---
if not question:
self.status = "๋๊ธฐ ์ค: ์ฌ์ฉ์ ์ง๋ฌธ์ด ์์ง ์
๋ ฅ๋์ง ์์์ต๋๋ค."
return DataFrame([])
qdrant = self._get_qdrant_client()
queries = self._expand_queries(question)
rows: list[dict] = []
for q in queries:
vec = self._embed_text(q)
hits = qdrant.search(collection_name=self.collection_name, query_vector=vec, limit=self.top_k,
with_payload=True, with_vectors=False,)
for h in hits:
payload = h.payload or {}
# content ํค ์๋ ํ์ง
content = (payload.get("content") or payload.get("page_content") or payload.get("text") or "")
# metadata ์๋ ํ์ง
metadata = (payload.get("metadata") or payload.get("meta") or {})
row = {"query": q, "score": h.score, "content": content, "metadata": metadata,}
# payload์ ๋ชจ๋ ํ๋๋ ์ถ๊ฐ
for k, v in payload.items():
if k not in row:
row[k] = v
rows.append(row)
df = DataFrame(rows)
self.status = f"{len(queries)}๊ฐ ์ฟผ๋ฆฌ๋ก {len(rows)}๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์์ต๋๋ค."
return df
DataFrame โ ํ ์คํธ๋ก ๋ณํ : ๋ฌธ์ ์ฒญํฌ๋ค์ ํ๋์ ๊ธด ๋ฌธ๋งฅ์ผ๋ก ์ฌ์กฐ๋ฆฝํ๋ ๋จ๊ณ
LLM์ด ๋ต๋ณํ ๋ ์ฌ์ฉํ ์ต์ข ํ๋กฌํํธ ๊ตฌ์ฑ๊ธฐ
๋น์ ์ ํ๊ณยท๊ฐ์ฌ ๋ถ์ผ์ ์ ํตํ ๊ฐ์ฌ ์ ๋ฌธ๊ฐ์
๋๋ค.
์๋๋ Multi-Query ๊ธฐ๋ฐ์ผ๋ก Qdrant์์ ๊ฒ์๋ ๊ฐ์ฌยทํ๊ณ ๊ด๋ จ ๋ฌธ์๋ค์ด๋ฉฐ,
์ค์ ์ฌ๋กยท์ง์ ์ฌํญยทํ๋กยท๊ฐ์ฌ์ง์นจ ๋ฑ์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.
์ด ๋ฌธ์๋ค์ ๊ทผ๊ฑฐ๋ก ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ํด ์ ํํ๊ณ ์ฌ์ค ๊ธฐ๋ฐ์ ๋ถ์์ ์ํํ์ธ์.
โโโโโโโโโโโโโโโโโโโโ
[์ฌ์ฉ์ ์ง๋ฌธ]
{question}
[๊ฒ์๋ ๋ฌธ๋งฅ]
{retrieved_docs}
โโโโโโโโโโโโโโโโโโโโ
์๋ ๊ท์น์ ๋ฐ๋ผ ๋ต๋ณ์ ์์ฑํ์ญ์์ค:
1) **๋ฌธ๋งฅ ๊ธฐ๋ฐ ์ฌ์ค ์ค๋ช
**
- ๋ฐ๋์ ์ ๊ณต๋ ๋ฌธ๋งฅ(retrieved_docs)์ ๊ทผ๊ฑฐํ์ฌ ์ค๋ช
ํ๋ค.
- ๋ฌธ๋งฅ์ ์๋ ๋ด์ฉ, ์ถ์ธก, ํ์ ์ ๋ณด๋ ์ ๋ ์ถ๊ฐํ์ง ์๋๋ค.
2) **ํต์ฌ ๋ต๋ณ ์ ์**
- ์ง๋ฌธ์ ๋ํ ๊ฒฐ๋ก ์ ๋จผ์ ์ ์ํ๋ค.
- ๊ฒฐ๋ก ์ ๊ทผ๊ฑฐ๊ฐ ๋๋ ๋ฌธ๋งฅ ์ ๋ฌธ์ฅยท๊ตฌ์ ์ ์์ฝ ๋๋ ์ง์ ์ธ์ฉํ๋ค.
3) **์ ์ฌ์ฌ๋ก ์ ์**
- ์ ๊ณต๋ ๋ฌธ๋งฅ(retrieved_docs) ์ค์์ ์ฌ์ฉ์ ์ง๋ฌธ๊ณผ ์ ์ฌํ๊ฑฐ๋ ๋
ผ๋ฆฌ์ ์ผ๋ก ์ฐ๊ฒฐ๋๋ ์ฌ๋ก๋ฅผ ์ฐพ์ ์์ฝํด ์ ์ํ๋ค.
- ์ ์ฌ์ฌ๋ก๋ โ๋ฌธ๋งฅ ์ ์ค์ ์กด์ฌํ๋ ์ฌ๋กโ๋ง ์ฌ์ฉํ๋ฉฐ, ๋ฌธ๋งฅ์ ์๋ ์ฌ๊ฑดยท๊ท์ ยทํ๋ก ๋ฑ์ ์์๋ก ๋ง๋ค์ด๋ด์ง ์๋๋ค.
4) **๊ท์ /๊ธฐ์ค ์ธ๊ธ**
- ๋ฌธ๋งฅ์ ํน์ ํ๊ณ๊ธฐ์ค(K-IFRS), ๊ฐ์ฌ์ง์นจ, ๋ฒ๋ น, ๊ท์ ์ด ์กด์ฌํ๋ ๊ฒฝ์ฐ์๋ง ์ ํํ ์ธ์ฉํ์ฌ ์ค๋ช
ํ๋ค.
- ์กด์ฌํ์ง ์๋ ๊ท์ ๋ช
ยทํ๋ก๋ช
์ ์์ฑํ์ง ์๋๋ค.
5) **์ ๋ณด ๋ถ์กฑ ์ ์ฒ๋ฆฌ**
- ๋ฌธ๋งฅ๋ง์ผ๋ก ํ๋จ์ด ์ด๋ ค์ด ๊ฒฝ์ฐ์ โ์ ๊ณต๋ ๋ฌธ๋งฅ๋ง์ผ๋ก๋ ํ๋จํ๊ธฐ ์ด๋ ต์ต๋๋ค.โ๋ผ๊ณ ๋ช
์ํ๋ค.
6) **์์ฑ ๋ฐฉ์**
- ์ ๋ฌธ์ ์ธ ๊ฐ์ฌยทํ๊ณ ์ฉ์ด ์ฌ์ฉ ๊ฐ๋ฅ.
- ๊ฒฐ๋ก โ ๊ทผ๊ฑฐ โ ์ ์ฌ์ฌ๋ก โ ์ถ๊ฐ ์ค๋ช
์์๋ก ๋
ผ๋ฆฌ์ ์ผ๋ก ์ ๋ฆฌํ๋ค.
- ์ค๋ช
์ ๊ฐ๊ฒฐํ๊ณ ๋ช
ํํ๊ฒ ์์ฑํ๋ค.
Prompt Template์์ ๋ง๋ ์ต์ข ํ๋กฌํํธ๋ฅผ ์ ๋ ฅ๋ฐ์ ์ค์ ์๋ต์ ์์ฑํ๋ LLM ์์ง
์ค์
Output: Chat Output์ผ๋ก ์ ๋ฌ
์ต์ข ์ ์ผ๋ก ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ฃผ๋ ๋ต๋ณ