Langflow - audit_rag_multi_reranker

JERRY·2025년 11월 30일

Project

목록 보기
6/14

✌️audit_rag_multi_reranker

ReRanker 기능 추가 및 Prompt Template 수정 → precision + relevance 향상

1. 플로우 전체 구조 개요

[1] ChatInput (사용자 질문)
        ↓
[2] MultiQuerySearch (확장된 쿼리 + Qdrant 벡터 검색)
        ↓  (DataFrame: 다수 문서 결과)
[3] ReRanker (HTTP 기반 OpenAI ReRank API) — 중요도 순 재정렬
        ↓  (DataFrame: 재배열된 문서 리스트)
[4] Parser (DataFrame → 문자열 context)
        ↓  (String / 텍스트)
[5] Prompt Template (context + user question → 최종 프롬프트)
        ↓
[6] OpenAI LLM (gpt-5) → 답변 생성
        ↓
[7] ChatOutput (최종 사용자 응답)

2. 각 컴포넌트별 상세 설명

(1) Chat Input

  • 사용자가 질문을 입력하는 노드
  • 이후 MultiQuerySearch의 user_question 입력으로 바로 연결됨

(2) MultiQuerySearch (Custom Component)

  • 역할

    사용자 질문을 여러 의미 방향으로 확장 (Multi-Query), 각 쿼리 별로 벡터화 → Qdrant에서 검색 → 모든 쿼리 결과를 하나의 DataFrame으로 병합

  • 입력값

    • collection_name: Qdrant 저장소 이름
    • OpenAI API Key (임베딩용)
    • Qdrant URL
    • user_question (Chat Input)
    • n_queries: 생성할 서브쿼리 개수
    • top_k: 각 쿼리당 검색할 문서 개수
  • 출력값

    • DataFrame: 각 결과 문서에 대해 query, score, content, metadata, 그 외 payload 필드 포함
  • 특징

    • 여러 쿼리에서 얻은 결과를 중복 포함 없이 통합 ⇒ recall 강화
    • 다양한 표현 방식의 쿼리를 통해 “유사 사례”, “관련 회계 처리 사례”, “처분 수위 사례”까지 폭넓게 탐색 가능

(3) ReRanker (Custom Component)

MultiQuery Search 만으로는 단순 벡터 유사도 기반 정렬 → 관련성은 보장되지만 실제 “질문 의도에 맞는 핵심 문서”가 상위에 오르지 않을 수 있음

  • 역할

    MultiQuery로 얻은 후보 문서들을 가져와, HTTP 기반 OpenAI ReRank API 로 재정렬 → 실제 질문에 더 밀접한 문서를 상위에 배치

  • 입력값

    • docs: DataFrame (MultiQuery 결과)
    • query: 사용자 질문 (Chat Input)
    • openai_api_key: ReRank API 호출용
    • model: 사용할 ReRank 모델 (기본 text-rerank-latest)
    • 출력값
    • DataFrame: relevance_score 기준으로 재정렬된 문서 리스트 (원본 row 그대로 유지, 순서만 변경)
  • 효과

    • semantic similarity + cross-document ranking → 더 의미 있고 “질문 의도에 맞는” 문서 우선

    • 노이즈 감소, prompt에 포함할 문서의 질과 밀도 향상

      왜 ReRanker가 중요한가?
      일반 벡터 검색은 “의미적으로 유사한 모든 문장”을 가져오기 때문에 질문에 가장 적절한 것은 뒤에 밀릴 수 있음
      ReRanker를 통하면 단순 유사도뿐 아니라 “문서 전체 문맥과 질문 간 관계성”을 기반으로 재정렬 가능
      즉, RAG 품질 향상

  • code

    from langflow.custom import Component
    from langflow.io import DataFrameInput, Output, MessageTextInput, StrInput, SecretStrInput
    from langflow.schema.dataframe import DataFrame
    import pandas as pd
    import requests, json
    
    class ReRanker(Component):
        display_name = "ReRanker"
        description = "Re-rank documents using OpenAI ReRank API (with debug logging)"
        icon = "sort"
        name = "ReRanker"
    
        inputs = [DataFrameInput(name="docs", display_name="Documents (DataFrame)", required=True),
                  MessageTextInput(name="query", display_name="User Question", required=True),
                  SecretStrInput(name="openai_api_key", display_name="OpenAI API Key", required=True),
                  StrInput(name="model", display_name="ReRank Model", value="text-rerank-latest", required=True),]
    
        outputs = [Output(name="reranked_docs",display_name="ReRanked Documents",method="run_rerank",output_type=DataFrame,)]
    
        async def run_rerank(self) -> DataFrame:
            df = self.docs
    
            # ---- DataFrame → dict list ----
            if isinstance(df, pd.DataFrame):
                rows = df.to_dict(orient="records")
            elif isinstance(df, dict):
                rows = df.get("data", [])
            else:
                rows = df
    
            if rows:
                first_row = rows[0]
                self.log(f"[DEBUG] FIRST ROW KEYS: {list(first_row.keys())}")
                self.log(f"[DEBUG] FIRST ROW RAW: {first_row}")
            else:
                self.status = "rows empty"
                return DataFrame([])
    
            # ---- query ----
            query_input = self.query
            query = getattr(query_input, "text", "") if query_input else ""
    
            # ---- documents ----
            documents = []
            for row in rows:
                content = (row.get("page_content") or row.get("content") or row.get("text") or "")
                documents.append(content)
    
            # ---- API call ----
            API_URL = "https://api.openai.com/v1/rerank"
    
            headers = {"Authorization": f"Bearer {self.openai_api_key}","Content-Type": "application/json"}
    
            payload = {"model": self.model,"query": query,"documents": documents,"top_n": len(documents)}
    
            try:
                response = requests.post(API_URL, headers=headers, data=json.dumps(payload))
                response.raise_for_status()
    
                api_data = response.json().get("data", [])
                self.log(f"[DEBUG] RERANK API RESPONSE: {api_data}")
    
            except Exception as e:
                self.status = f"API Error: {e}"
                return DataFrame(rows)
    
            # ---- ranking ----
            ranked_rows = sorted(zip(rows, api_data), key=lambda x: x[1].get("relevance_score", 0), reverse=True,)
            reranked_rows = [row for row, score in ranked_rows]
            self.status = f"ReRanking 완료: {len(reranked_rows)}개 문서 재정렬"
    
            return DataFrame(reranked_rows)

(4) Parser (DataFrame → 문자열 context)

  • 역할

    ReRanker가 반환한 DataFrame의 각 row를 사람이 읽기 좋고 LLM이 digest하기 좋은 텍스트 블록으로 변환

  • 구체 동작

    • content, metadata 등의 column을 적절히 포맷팅
    • 여러 문서를 하나의 긴 context string 으로 조합
  • 이유

    • LLM(예: OpenAI GPT)는 DataFrame 구조를 이해하지 못함
    • String context로 변환해야 prompt에 자연스럽게 포함 가능

(5) Prompt Template

Parser로 생성된 문맥(context) + 사용자 질문을 기반으로 회계/감사 전문가 역할 최종 프롬프트를 생성

당신은 회계·감사 분야에 전문성을 갖춘 상급 감사 전문가입니다.  
아래에는 Multi-Query 검색 및 ReRanker를 통해 선별된 실제 감사·회계 문서들이 제공됩니다.  
이 문서들은 감사보고서, 지적사항, 판례, 감사지침 등을 기반으로 한 “사실 기반 근거 자료”입니다.

당신의 역할은 다음 문맥(retrieved_docs)에 기반하여  
사용자의 질문에 대해 **정확하고 사실 기반이며, 규칙을 준수한 감사 전문가식 답변**을 생성하는 것입니다.

────────────────────
 **사용자 질문**
{question}

 **검색된 문맥(필수 근거 자료)**
{retrieved_docs}
────────────────────

# 답변 작성 규칙 (Strict Mode)

### 1) **문맥 기반 사실 설명 (Hallucination 금지)**
- 모든 설명·근거·사례는 반드시 {retrieved_docs} 내 실제 내용에서만 가져온다.  
- 문맥에 존재하지 않는 사실, 규정, 수치, 사례, 용어는 절대 생성하지 않는다.
- 문맥에 없는 정보는 “문맥에 해당 정보가 존재하지 않습니다.”라고 명시한다.

### 2) **결론 우선 (Top-Down 방식)**
- 첫 문단에 질문에 대한 결론을 명확하고 간결하게 제시한다.  
- 결론 다음 줄부터 근거를 정리한다.

### 3) **근거 제시 방식**
- 근거는 문맥 속 텍스트를 **직접 인용**하거나 **요약**하여 제시한다.  
- 인용 시 아래 형식을 사용한다:
  - “문맥 인용: …(본문 일부)…”
  - 또는 “문맥 요약: …”

### 4) **유사사례 제시**
- 문맥에서 사용자 질문과 유사한 사례가 있다면 1~2개만 선택하여 요약한다.  
- 유사사례는 반드시 실제 문맥 속 문서에서만 발췌해야 한다.

### 5) **규정/기준 인용 규칙**
- 문맥에 K-IFRS, 감사지침, 법령, 규정, 회계기준 등이 명시된 경우에만 정확히 인용한다.
- 문맥에 언급되지 않은 규정명·법령·판례는 새로 만들어내지 않는다.

### 6) **정보 부족 시 처리**
- 문맥 내 근거가 부족하면 아래와 같이 명확히 언급한다:  
  - “제공된 문맥만으로는 해당 질문에 대한 확정적인 판단이 어렵습니다.”

### 7) **작성 방식**
- 마크다운 형식으로 작성.
- 구조는 반드시 다음 순서를 따른다:

---
## 1. 결론  
(핵심 답변)

## 2. 근거 (문맥 기반)
- 문맥 인용 또는 요약
- 이유 설명

## 3. 유사사례 (문맥 기반)
- 실제 문맥 내 사례 요약

## 4. 추가 설명 또는 한계
- 규정/기준 인용 (문맥에 있을 경우)
- 정보 부족 시 처리 문장 포함
---

반드시 위 규칙을 모두 준수하여 답변하라.

(6) OpenAI LLM (gpt-5)

Prompt Template이 만든 최종 프롬프트를 입력 → 사용자 질문 + 선택된 문맥에 기반한 답변 생성

(7) Chat Output

최종 GPT 응답을 사용자에게 전달

3. 플로우 결과

기대 효과

  • 다양한 쿼리 확장(MultiQuery) → recall 강화
  • ReRanker 추가 → precision + relevance 향상
  • Prompt Template 수정 → 근거 있는, 전문화된 답변

주의점 및 한계점

  • ReRanker 호출 시 HTTP 요청 오버헤드 발생 → 처리 시간 증가
  • 벡터 검색 결과 + ReRanker 재정렬 후 너무 많은 문서를 prompt에 넣으면 context 길이 초과 가능성이 있음 → 필요 시 상위 N개만 사용하거나, Parser에서 요약/압축 필요

0개의 댓글