Langflow - audit_rag_MultiQuery

JERRYยท2025๋…„ 11์›” 30์ผ

Project

๋ชฉ๋ก ๋ณด๊ธฐ
5/14

๐Ÿ‘†audit_rag_MultiQuery

MultiQuery ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ๋ฐ Prompt Template ์ˆ˜์ • โ†’ recall ๊ฐ•ํ™”

1. ํ”Œ๋กœ์šฐ ์ „์ฒด ๊ตฌ์กฐ ๊ฐœ์š”

[1] Chat Input
        โ†“  (์‚ฌ์šฉ์ž ์งˆ๋ฌธ ์ „๋‹ฌ)
[2] MultiQuery Search (Custom Component)
        โ†“  (DataFrame ๋ฐ˜ํ™˜)
[3] Parser (DataFrame โ†’ ๋ฌธ์ž์—ด context)
        โ†“
[4] Prompt Template (context + question)
        โ†“
[5] OpenAI (LLM)
        โ†“
[6] Chat Output

2. ๊ฐ ์ปดํฌ๋„ŒํŠธ๋ณ„ ์ƒ์„ธ ์„ค๋ช…

(1) Chat Input

์‚ฌ์šฉ์ž๊ฐ€ ์งˆ๋ฌธ์„ ์ž…๋ ฅํ•˜๋Š” ์‹œ์ž‘์ 

  • MultiQuery Search์˜ user_question ์ž…๋ ฅ ํฌํŠธ์— ์—ฐ๊ฒฐ๋จ
  • MultiQuery Search๊ฐ€ ์งˆ๋ฌธ์„ ๋ฐ›์ง€ ๋ชปํ•˜๋ฉด ๋ฐ”๋กœ ์˜ค๋ฅ˜ ๋ฐœ์ƒ

(2) MultiQuery Search (Custom Component)

  • ์—ญํ• 

    • ์‚ฌ์šฉ์ž ์งˆ๋ฌธ์„ ์—ฌ๋Ÿฌ ์˜๋ฏธ ๋ฐฉํ–ฅ์œผ๋กœ ํ™•์žฅ (MultiQuery)
    • ๊ฐ ์ฟผ๋ฆฌ๋ฅผ OpenAI ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ๋กœ ๋ฒกํ„ฐํ™”
    • Qdrant์—์„œ ์ฟผ๋ฆฌ๋ณ„ Top-K ๊ฒ€์ƒ‰ ์‹คํ–‰
    • ๋ชจ๋“  ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ํ•˜๋‚˜์˜ DataFrame ํ˜•ํƒœ๋กœ ํ†ตํ•ฉ
  • ์ž…๋ ฅ๊ฐ’

    Input์„ค๋ช…
    collection_nameQdrant ์ €์žฅ์†Œ ์ด๋ฆ„
    openai_api_keyembedding์šฉ
    qdrant_urlQdrant ์„œ๋ฒ„ URL
    user_questionChatInput์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์งˆ๋ฌธ
    n_queries์ƒ์„ฑํ•  ์„œ๋ธŒ์ฟผ๋ฆฌ ๊ฐœ์ˆ˜
    top_k๊ฐ ์ฟผ๋ฆฌ๋‹น ๊ฒ€์ƒ‰ํ•  ๋ฌธ์„œ ๊ฐœ์ˆ˜
  • ์ถœ๋ ฅ๊ฐ’

    • search_results: DataFrame
    • ์—ฌ๋Ÿฌ ์ฟผ๋ฆฌ์—์„œ ๋‚˜์˜จ ๋ฌธ์„œ๋“ค์ด ํ•˜๋‚˜์˜ ํ†ตํ•ฉ ํ…Œ์ด๋ธ”๋กœ ์ถœ๋ ฅ
      ex ) | query | score | content | metadata | ... |
  • code

    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

(3) Parser

DataFrame โ†’ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ : ๋ฌธ์„œ ์ฒญํฌ๋“ค์„ ํ•˜๋‚˜์˜ ๊ธด ๋ฌธ๋งฅ์œผ๋กœ ์žฌ์กฐ๋ฆฝํ•˜๋Š” ๋‹จ๊ณ„

  • MultiQuery Search๋Š” DataFrame์„ ์ถœ๋ ฅํ•จ โ†’ LLM์ด ๊ทธ๋Œ€๋กœ ์ฝ์„ ์ˆ˜ ์—†์Œ
    • DataFrame์˜ ๊ฐ row๋ฅผ string์œผ๋กœ ์ •๋ฆฌ
    • LLM์ด ์ดํ•ด ๊ฐ€๋Šฅํ•œ โ€œcontext blockโ€์œผ๋กœ ๊ฐ€๊ณต
    • {context} ์ž๋ฆฌ์— ๋“ค์–ด๊ฐˆ ๋ฌธ์ž์—ด ์ƒ์„ฑ

(4) Prompt Template

LLM์ด ๋‹ต๋ณ€ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ตœ์ข… ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ๊ธฐ

  • ์ž…๋ ฅ ์š”์†Œ

    • context (Parser์—์„œ ์ „๋‹ฌ)
    • question (ChatInput ์งˆ๋ฌธ ๋‹ค์‹œ ์ „๋‹ฌ)
  • ํ…œํ”Œ๋ฆฟ์˜ ๋ชฉ์ 

    • ๊ฒ€์ƒ‰๋œ ๋ฌธ์„œ๋ฅผ ๊ทผ๊ฑฐ๋กœ ๋‹ต๋ณ€ํ•˜๋„๋ก ๊ฐ•์ œ
    • ์œ ์‚ฌ์‚ฌ๋ก€ ํฌํ•จ
    • ํ—ˆ์œ„ ์ •๋ณด ๊ธˆ์ง€
    • ๊ฐ์‚ฌยทํšŒ๊ณ„ ๋ถ„์•ผ์— ํŠนํ™”๋œ ์—ญํ•  ๋ถ€์—ฌ
    ๋‹น์‹ ์€ ํšŒ๊ณ„ยท๊ฐ์‚ฌ ๋ถ„์•ผ์— ์ •ํ†ตํ•œ ๊ฐ์‚ฌ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.
    ์•„๋ž˜๋Š” Multi-Query ๊ธฐ๋ฐ˜์œผ๋กœ Qdrant์—์„œ ๊ฒ€์ƒ‰๋œ ๊ฐ์‚ฌยทํšŒ๊ณ„ ๊ด€๋ จ ๋ฌธ์„œ๋“ค์ด๋ฉฐ,
    ์‹ค์ œ ์‚ฌ๋ก€ยท์ง€์ ์‚ฌํ•ญยทํŒ๋ก€ยท๊ฐ์‚ฌ์ง€์นจ ๋“ฑ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
    
    ์ด ๋ฌธ์„œ๋“ค์„ ๊ทผ๊ฑฐ๋กœ ์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ์— ๋Œ€ํ•ด ์ •ํ™•ํ•˜๊ณ  ์‚ฌ์‹ค ๊ธฐ๋ฐ˜์˜ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”.
    
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    [์‚ฌ์šฉ์ž ์งˆ๋ฌธ]
    {question}
    
    [๊ฒ€์ƒ‰๋œ ๋ฌธ๋งฅ]
    {retrieved_docs}
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    
    ์•„๋ž˜ ๊ทœ์น™์— ๋”ฐ๋ผ ๋‹ต๋ณ€์„ ์ž‘์„ฑํ•˜์‹ญ์‹œ์˜ค:
    
    1) **๋ฌธ๋งฅ ๊ธฐ๋ฐ˜ ์‚ฌ์‹ค ์„ค๋ช…**
    - ๋ฐ˜๋“œ์‹œ ์ œ๊ณต๋œ ๋ฌธ๋งฅ(retrieved_docs)์— ๊ทผ๊ฑฐํ•˜์—ฌ ์„ค๋ช…ํ•œ๋‹ค.
    - ๋ฌธ๋งฅ์— ์—†๋Š” ๋‚ด์šฉ, ์ถ”์ธก, ํ—ˆ์œ„ ์ •๋ณด๋Š” ์ ˆ๋Œ€ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๋Š”๋‹ค.
    
    2) **ํ•ต์‹ฌ ๋‹ต๋ณ€ ์ œ์‹œ**
    - ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๊ฒฐ๋ก ์„ ๋จผ์ € ์ œ์‹œํ•œ๋‹ค.
    - ๊ฒฐ๋ก ์˜ ๊ทผ๊ฑฐ๊ฐ€ ๋˜๋Š” ๋ฌธ๋งฅ ์† ๋ฌธ์žฅยท๊ตฌ์ ˆ์„ ์š”์•ฝ ๋˜๋Š” ์ง์ ‘ ์ธ์šฉํ•œ๋‹ค.
    
    3) **์œ ์‚ฌ์‚ฌ๋ก€ ์ œ์‹œ**
    - ์ œ๊ณต๋œ ๋ฌธ๋งฅ(retrieved_docs) ์ค‘์—์„œ ์‚ฌ์šฉ์ž ์งˆ๋ฌธ๊ณผ ์œ ์‚ฌํ•˜๊ฑฐ๋‚˜ ๋…ผ๋ฆฌ์ ์œผ๋กœ ์—ฐ๊ฒฐ๋˜๋Š” ์‚ฌ๋ก€๋ฅผ ์ฐพ์•„ ์š”์•ฝํ•ด ์ œ์‹œํ•œ๋‹ค.
    - ์œ ์‚ฌ์‚ฌ๋ก€๋Š” โ€œ๋ฌธ๋งฅ ์† ์‹ค์ œ ์กด์žฌํ•˜๋Š” ์‚ฌ๋ก€โ€๋งŒ ์‚ฌ์šฉํ•˜๋ฉฐ, ๋ฌธ๋งฅ์— ์—†๋Š” ์‚ฌ๊ฑดยท๊ทœ์ •ยทํŒ๋ก€ ๋“ฑ์€ ์ž„์˜๋กœ ๋งŒ๋“ค์–ด๋‚ด์ง€ ์•Š๋Š”๋‹ค.
    
    4) **๊ทœ์ •/๊ธฐ์ค€ ์–ธ๊ธ‰**
    - ๋ฌธ๋งฅ์— ํŠน์ • ํšŒ๊ณ„๊ธฐ์ค€(K-IFRS), ๊ฐ์‚ฌ์ง€์นจ, ๋ฒ•๋ น, ๊ทœ์ •์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ •ํ™•ํžˆ ์ธ์šฉํ•˜์—ฌ ์„ค๋ช…ํ•œ๋‹ค.
    - ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ทœ์ •๋ช…ยทํŒ๋ก€๋ช…์€ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค.
    
    5) **์ •๋ณด ๋ถ€์กฑ ์‹œ ์ฒ˜๋ฆฌ**
    - ๋ฌธ๋งฅ๋งŒ์œผ๋กœ ํŒ๋‹จ์ด ์–ด๋ ค์šด ๊ฒฝ์šฐ์—” โ€œ์ œ๊ณต๋œ ๋ฌธ๋งฅ๋งŒ์œผ๋กœ๋Š” ํŒ๋‹จํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.โ€๋ผ๊ณ  ๋ช…์‹œํ•œ๋‹ค.
    
    6) **์ž‘์„ฑ ๋ฐฉ์‹**
    - ์ „๋ฌธ์ ์ธ ๊ฐ์‚ฌยทํšŒ๊ณ„ ์šฉ์–ด ์‚ฌ์šฉ ๊ฐ€๋Šฅ.
    - ๊ฒฐ๋ก  โ†’ ๊ทผ๊ฑฐ โ†’ ์œ ์‚ฌ์‚ฌ๋ก€ โ†’ ์ถ”๊ฐ€ ์„ค๋ช… ์ˆœ์„œ๋กœ ๋…ผ๋ฆฌ์ ์œผ๋กœ ์ •๋ฆฌํ•œ๋‹ค.
    - ์„ค๋ช…์€ ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•˜๊ฒŒ ์ž‘์„ฑํ•œ๋‹ค.

(5) OpenAI ๋…ธ๋“œ

Prompt Template์—์„œ ๋งŒ๋“  ์ตœ์ข… ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ ์‹ค์ œ ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๋Š” LLM ์—”์ง„

  • ์„ค์ •

    • Model: gpt-5
  • Output: Chat Output์œผ๋กœ ์ „๋‹ฌ

(6) Chat Output

์ตœ์ข…์ ์œผ๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ฃผ๋Š” ๋‹ต๋ณ€

3. ํ”Œ๋กœ์šฐ ๊ฒฐ๊ณผ

๊ธฐ๋Œ€ ํšจ๊ณผ

  • ํ•ญ์ƒ 10~20๊ฐœ ์ด์ƒ์˜ ๊ด€๋ จ ๊ฐ์‚ฌ ์‚ฌ๋ก€ ์ˆ˜์ง‘
  • LLM์ด ๋ฌธ์„œ ๊ทผ๊ฑฐ ๊ธฐ๋ฐ˜์œผ๋กœ ์•ˆ์ •์ ์œผ๋กœ ์ž‘์„ฑ
  • ์œ ์‚ฌ์‚ฌ๋ก€๊นŒ์ง€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํฌํ•จ๋œ ๋‹ต๋ณ€ ์ƒ์„ฑ
  • ๊ฐ์‚ฌ์ง€์ ๋ฌธ์„œ์™€ ์‹ค์ œ ๊ทœ์ •/์ง€์นจ ๊ทผ๊ฑฐ๊ฐ€ ์ž˜ ๋ฐ˜์˜๋จ

0๊ฐœ์˜ ๋Œ“๊ธ€