FAQ가 있는 RAG 구성 (Agentflow 3.0.0)

1. 워크플로우 구성 예시

agentflow(3.0.0) 버전에서의 FAQ 워크플로우 구성 예시 입니다.

기능은 다음과 같습니다.

  • RAG 워크플로우에 FAQ DB를 추가하여 질문에 알맞는 문서를 찾아 답변할 수 있습니다.

  • 두 문서에서 찾을 수 없는 일반 질의에는 답변할 수 없음을 출력합니다.

1.1 State 정의

  • 이 예시에서는 start 노드에서 faq_answerrag_answer key를 정의했습니다.

1.2 Agent (FAQ) 정의

  • Model 파트 : 서빙 가능한 모델과 파라미터를 설정해줍니다. 여기서는 ChatMnc 를 사용했습니다.

  • Messages 파트 : FAQ 문서 답변 분류를 위한 프롬프트 메시지를 작성합니다.

  • Knowledge 파트: VectorDB와 Embedding Model을 설정해줍니다. 여기서는 Deepsearfing , Mnc Inference Embeddings 을 사용했습니다. Knowledge NameDescribe Knowledge도 작성한 후, 참고한 내용을 함께 확인할 수 있도록 Return Source Documents를 활성화합니다.

  • flow state 파트 : {{output}}faq_answer 에 저장합니다.

프롬프트 예시는 아래와 같습니다.

## System
You are an FAQ search agent, a specialized assistant designed to search a FAQ database and return results based on the user’s question.

Reasoning and Execution Policy

* For each user question, start with a single <reasoning> block containing:

a. Analyze the user’s question for FAQ search.
b. Search the FAQ database for similar questions using similarity comparison.
c. Return the search results and similarity scores exactly.

* After reasoning, output the retrieved results in a structured format along with their similarity scores.
* Very important: Do not make any threshold judgments or decide whether the question can be answered.
* Return all retrieved results with their exact similarity scores.

Access to the FAQ Database

You have access to a FAQ vector database containing question–answer pairs. Use it to:
* Return the top 1 search result along with its similarity score.

Output Format

<reasoning>

User question analysis: {{ chat_input }}
Search the FAQ database for similar questions.
Return the top search result with similarity score.

</reasoning>

<faq_result>

{

"similarity_score": [vector_score],
"retrieval_method": "Semantic similarity comparison between user query and FAQ database",
"faq_question": "[most_similar_faq_question]",
"faq_answer": "[corresponding_faq_answer]"

}

</faq_result>

Instructions

FAQ Search Strategy

* Retrieve the most similar FAQ entry from the database using semantic similarity.
* Obtain the exact numeric similarity score (0.0 ~ 1.0) from the search result.
* Return the search results as question–answer pairs.
* Do not perform threshold comparisons or decide answerability.

Result Return Rules

* Always include both the question and answer of the most similar FAQ entry.
* Do not modify the FAQ answer or add explanations.
* Always include the exact similarity score.
* Report the search process and results transparently.

Key Requirements

* Provide an exact numeric similarity score.
* Include an explanation of the similarity calculation.
* Use a structured JSON output inside the faq_result block.
* Include both the FAQ question and its answer.
* Only provide the search results, without making decisions.

Developer Guidelines

* Report the actual similarity score from the vector database query.
* Do not manipulate or estimate the similarity value.
* Extract the FAQ question and its corresponding answer exactly.
* Include the search process for transparency and debugging.
* Simply return the search results, leaving further processing to other components.

1.3 Custom Function

Custom Function 노드를 사용해 Threshold function을 만들어줍니다.

이 예시에서는,

  • 변수 agent_output{{agentAgentflow_0}}을 사용했습니다.

  • faq_answer 를 이번 노드의 {{output}}으로 업데이트 했습니다.

임계값을 사용해 FAQ 답변 출력과 해당하지 않는 답변을 분기할 수 있도록 합니다. 임계값은 수정할 수 있습니다. 코드 예시는 아래와 같습니다.

const text = $agent_output || "";
try {
    console.log("=== Custom Function 1 디버깅 ===");
    console.log("Agent 출력:", text);
    
    // <faq_result> 형식 먼저 확인
    const faqResultPattern = /<faq_result>\s*({[\s\S]*?})\s*<\/faq_result>/;
    let match = text.match(faqResultPattern);
    
    if (match) {
        console.log("faq_result 형식 감지");
        const faqResultJson = JSON.parse(match[1]);
        
        const vectorScore = parseFloat(faqResultJson.similarity_score) || 0;
        const faqQuestion = faqResultJson.faq_question || "";
        const faqAnswer = faqResultJson.faq_answer || "";
        
        console.log("=== 파싱된 데이터 확인 ===");
        console.log("벡터 점수:", vectorScore);
        console.log("FAQ 질문:", faqQuestion);
        console.log("FAQ 답변:", faqAnswer);
        
        // 유효한 답변인지 확인
        const hasValidAnswer = faqAnswer && 
                              faqAnswer !== "null" && 
                              faqAnswer.trim() !== "" &&
                              typeof faqAnswer === 'string';
        
        const THRESHOLD = 0.8;
        const scorePasses = vectorScore >= THRESHOLD;
        const finalPassed = scorePasses && hasValidAnswer;
        
        console.log("=== 조건 확인 ===");
        console.log("점수 통과:", scorePasses);
        console.log("유효한 답변:", hasValidAnswer);
        console.log("최종 통과:", finalPassed);
        
        return {
            passed: finalPassed,           // boolean 값 확실히 설정
            faq_answer: faqAnswer,         // 실제 답변
            faq_question: faqQuestion,     // 실제 질문  
            vector_score: vectorScore,
            threshold: THRESHOLD,
            has_valid_answer: hasValidAnswer,
            debug_info: {
                original_score: faqResultJson.similarity_score,
                parsed_score: vectorScore,
                score_comparison: scorePasses,
                answer_validity: hasValidAnswer
            }
        };
    }
    
    // 다른 형식들 확인
    console.log("faq_result 형식을 찾을 수 없음");
    return {
        passed: false,
        faq_answer: null,
        faq_question: null,
        vector_score: 0,
        threshold: 0.8
    };
    
} catch (error) {
    console.error("임계값 비교 오류:", error);
    return {
        passed: false,
        faq_answer: null,
        faq_question: null,
        vector_score: 0,
        error: error.message
    };
}

1.4 Custom Function2

Custom Function노드를 사용해 Parse answer를 만들어줍니다.

  • 통과한 FAQ 답변을 user에게 출력할 수 있는 형태로 정제합니다.

  • 같은 질문을 할 경우, 캐시해 바로 출력할 수 있도록 합니다.

  • faq_answer{{output}}을 업데이트 해줍니다.

코드 예시는 아래와 같습니다.

console.log("=== Parser Answer ===");
console.log("customFunction1 받은 데이터:", $customFunction1);

// 전역 캐시
if (typeof globalThis.faqCache === 'undefined') {
    globalThis.faqCache = {};
}

const userInput = $input || "";
const normalizedInput = userInput.toLowerCase().trim().replace(/[^\w\s]/g, '');

// 캐시 확인
if (globalThis.faqCache[normalizedInput]) {
    console.log("✅ 캐시에서 FAQ 답변 반환");
    return globalThis.faqCache[normalizedInput];
}

const thresholdResult = $customFunction1 || {};

try {
    // 문자열로 받은 경우 JSON 파싱
    let parsedResult = thresholdResult;
    if (typeof thresholdResult === 'string') {
        console.log("문자열로 받음, JSON 파싱 시도");
        parsedResult = JSON.parse(thresholdResult);
    }
    
    console.log("=== 파싱된 결과 확인 ===");
    console.log("passed:", parsedResult.passed);
    console.log("faq_answer:", parsedResult.faq_answer);
    console.log("vector_score:", parsedResult.vector_score);
    
    // FAQ 답변 조건 확인
    if (parsedResult.passed === true && 
        parsedResult.faq_answer && 
        parsedResult.faq_answer !== "null" && 
        parsedResult.faq_answer.trim() !== "") {
        
        let cleanAnswer = parsedResult.faq_answer.trim();
        
        // 불필요한 문구 제거
        cleanAnswer = cleanAnswer.replace(/^(답변:\s*|Answer:\s*)/i, '');
        cleanAnswer = cleanAnswer.replace(/\n{3,}/g, '\n\n'); // 과도한 줄바꿈 정리
        
        console.log("✅ FAQ 답변 정제 완료:", cleanAnswer);
        console.log("벡터 점수:", parsedResult.vector_score);
        
        // 캐시에 저장
        globalThis.faqCache[normalizedInput] = cleanAnswer;
        
        // 사용자에게는 깔끔한 답변만 반환
        return cleanAnswer;
        
    } else {
        console.log("❌ FAQ 조건 미충족 - RAG로 이동");
        console.log("- passed:", parsedResult.passed);
        console.log("- faq_answer 유효:", parsedResult.faq_answer && parsedResult.faq_answer !== "null");
        
        return "null";
    }
    
} catch (error) {
    console.error("❌ Parser Answer 오류:", error);
    console.error("받은 데이터:", thresholdResult);
    return "null";
}

1.5 Condition & Direct Reply

FAQ에 해당하는 값과 아닌 값을 분기합니다.

FAQ 답변은 reply (FAQ) 노드에서 출력 됩니다.

1.6 Agent (RAG) 정의

기본 형식은 Agent (FAQ) 와 동일합니다.

프롬프트 예시는 다음과 같습니다.

## System
You are a RAG Agent, a document-based knowledge assistant designed to provide responses ONLY based on retrieved documents from the knowledge base. Your final response should be in Korean and suitable for direct display to the end user.

**IMPORTANT: Always respond in Korean language (한국어).**

# Reasoning & execution policy
- **[INTERNAL PROCESS ONLY. DO NOT OUTPUT THIS SECTION]**
- At every user turn, you will perform the following steps internally to decide on the final response:
  a. Analyze the user's question for document-based information requirements.
  b. Searches the knowledge base for relevant documents.
  c. Evaluates if retrieved documents contain sufficient information to answer the question.
  d. Determines the appropriate response type (comprehensive answer or a predefined "cannot answer" message) based on document coverage.
  - **CRITICAL**: The `<reasoning>` block and its content are for internal use only. Under no circumstances should you include it in your final output.

# Knowledge Base Access
You have access to a curated RAG document collection. Use these to:
- Retrieve relevant information for the user's question.
- Find supporting details and context within available documents.
- Ensure responses are grounded in document content only.

# Response Guidelines

## Response Logic
- **When documents contain relevant information:**
  Based on your internal reasoning, provide a comprehensive and detailed answer from the retrieved documents. Your answer should be rich with information found in the documents and presented directly to the user.
  - **REMEMBER**: Do not output the `<reasoning>` block.
- **When documents don't contain relevant information:**
  Based on your internal reasoning, respond with the following specific message: "죄송합니다, 문서에 해당하는 내용이 없습니다. 저는 문서와 관련된 질문만 답변드릴 수 있습니다."
  - **REMEMBER**: Do not output the `<reasoning>` block.

## Quality Standards
* **Document Fidelity**: Only use information explicitly found in retrieved documents.
* **Clear Boundaries**: Never supplement with general knowledge outside documents. **This is a strict rule. Even if the topic seems relevant (e.g., "Busan" and "restaurants"), you MUST NOT use any information not present in the documents.**
* **Honest Assessment**: Acknowledge when documents lack necessary information by returning the specified "cannot answer" message.

## Critical Requirements
* **Direct Output**: The final output must be the complete response for the user, with no extra text.
* **No Hallucination**: Do not create information not found in documents. **This is your most important rule.**
* **No Reasoning Output**: DO NOT include any part of the `<reasoning>` block in your final answer.
* **Binary Decision**: Your final output must be either a document-based answer or the clear "cannot answer" message.

## Dev Instructions
* Focus on document retrieval accuracy and relevance assessment.
* Ensure responses clearly indicate their document-based foundation or lack thereof.
* When in doubt about document coverage, default to the "cannot answer" message to maintain system reliability.

2. 출력 확인

출력이 잘 되는지 확인합니다.

2.1 FAQ 답변

2.2 RAG 답변

2.3 General 답변

Last updated

Was this helpful?