강의

멘토링

커뮤니티

인프런 커뮤니티 질문&답변

Stanley Choi님의 프로필 이미지
Stanley Choi

작성한 질문수

LangGraph를 활용한 AI Agent 개발 (feat. MCP)

2.4 생성된 답변을 여러번 검증하는 Self-RAG

query 에 대한 answer 결과값이 나오지 않습니다.

작성

·

55

0

image.png

강의 모두 코드 똑같이 돌리고 db만 pinecone 을 사용하였습니다 ! 결과에 강의처럼 answer이 나오지 않는데 원인을 잘 모르겠습니다 ㅜ
gpt 에 검색해보면
전체 코드를 꼼꼼히 검토해본 결과, 질문하신 "결과(answer)가 나오지 않는 이유"는 크게 두 가지입니다. 하나는 데이터가 유실되는 방식의 반환(return) 때문이고, 다른 하나는 무한 루프(Infinite Loop) 가능성 때문입니다.


1. 가장 큰 원인: State 데이터 유실

LangGraph의 각 노드 함수(retrieve, generate, rewrite 등)는 AgentState를 반환할 때, 기존의 데이터를 포함해서 돌려주어야 합니다.

현재 작성하신 코드를 보면:

  • retrieve 함수: return {'context': docs} (이때 query가 사라짐)

  • generate 함수: return {'answer': response.content} (이때 query, context가 모두 사라짐)


    이렇게 나오는데 제가 봤을땐 이게 원인은 아닌것같습니다...!

답변 2

0

Stanley Choi님의 프로필 이미지
Stanley Choi
질문자

코드 올려드립니다! 시간 되실때 확인 한번 부탁드립니다:)

0

제이쓴님의 프로필 이미지
제이쓴
지식공유자

Context만 있고 답변이 나오지 않는 상황인가요? 코드 전체를 올려주시면 디버깅을 시도해 볼 수 있을 것 같습니다~

Stanley Choi님의 프로필 이미지
Stanley Choi
질문자

from langchain_pinecone import PineconeVectorStore
from pinecone import Pinecone
import dotenv
from langchain_openai import OpenAIEmbeddings

dotenv.load_dotenv()
embedding = OpenAIEmbeddings(model='text-embedding-3-large')

pc =Pinecone()

index_name = "tax-index-langgraph"

index = pc.Index(index_name)
vectorstore = PineconeVectorStore(embedding=embedding, index=index)


retriever = vectorstore.as_retriever(search_kwargs={'k':3})
from typing_extensions import List, TypedDict
from langchain_core.documents import Document
from langgraph.graph import StateGraph

class AgentState(TypedDict):
    query: str
    context: List[Document]
    answer: str
    
graph_builder = StateGraph(AgentState)
def retrieve(state: AgentState) -> AgentState:
    
    query = state['query']
    docs = retriever.invoke(query)
    return {'context': docs}
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model='gpt-4o')
# Create a LangSmith API in Settings > API Keys
# Make sure API key env var is set:
# import os; os.environ["LANGSMITH_API_KEY"] = "<your-api-key>"
from langsmith import Client

client = Client()
generate_prompt = client.pull_prompt("rlm/rag-prompt")

# 지정된 매개변수로 언어 모델을 초기화합니다


def generate(state: AgentState) -> AgentState:
    
    # state에서 컨텍스트와 쿼리를 추출합니다
    context = state['context']
    query = state['query']
    
    # 작업 체인을 생성합니다: 먼저 프롬프트를 검색하고, 그 다음 응답을 생성합니다
    rag_chain = generate_prompt | llm
    
    # 쿼리와 컨텍스트로 체인을 호출하여 응답을 얻습니다
    response = rag_chain.invoke({'question': query, 'context': context})
    
    # 생성된 답변을 반환합니다
    return {'answer': response}
# Create a LangSmith API in Settings > API Keys
# Make sure API key env var is set:
# import os; os.environ["LANGSMITH_API_KEY"] = "<your-api-key>"
from langsmith import Client
from typing import Literal

client = Client()
doc_relevance_prompt = client.pull_prompt("langchain-ai/rag-document-relevance")

def check_doc_relevance(state: AgentState) -> Literal['relevant', 'irrelevant']:
   
    query = state['query']
    context = state['context']
    context_text = "\n\n".join([doc.page_content for doc in context])
    doc_relevance_chain = doc_relevance_prompt | llm
    response = doc_relevance_chain.invoke({'question': query, 'documents': context_text})

    print(f'response: {response['Score']}')
    if response['Score'] == 1:
        # 2.3장과 다르게 `relevant`와 `irrelevant`를 반환합니다
        # node를 직접 지정하는 것보다 실제 판단 결과를 리턴하면서 해당 node의 재사용성을 높일 수 있습니다.
        return 'relevant'
    
    return 'irrelevant'
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser

dictionary = ['사람과 관련된 표현 -> 거주자']

rewrite_prompt = PromptTemplate.from_template(f"""
사용자의 질문을 보고, 우리의 사전을 참고해서 사용자의 질문을 변경해주세요 
사전: {dictionary}                                           
질문: {{query}}
""")

def rewrite(state: AgentState) -> AgentState:
   
    query = state['query']
    rewrite_chain = rewrite_prompt | llm | StrOutputParser()

    response = rewrite_chain.invoke({'query': query})
    return {'query': response}
# set the LANGCHAIN_API_KEY environment variable (create key in settings)
from langchain_core.output_parsers import StrOutputParser

hallucination_prompt = PromptTemplate.from_template("""
You are a teacher tasked with evaluating whether a student's answer is based on documents or not,
Given documents, which are excerpts from income tax law, and a student's answer;
If the student's answer is based on documents, respond with "not hallucinated",
If the student's answer is not based on documents, respond with "hallucinated".

documents: {documents}
student_answer: {student_answer}
""")

hallucination_llm = ChatOpenAI(model='gpt-4o', temperature=0)

def check_hallucination(state: AgentState) -> Literal['hallucinated', 'not hallucinated']:
    answer = state['answer']
    context = state['context']
    context = [doc.page_content for doc in context]
    hallucination_chain = hallucination_prompt | hallucination_llm | StrOutputParser()
    response = hallucination_chain.invoke({'student_answer': answer, 'documents': context})

    return response
# Create a LangSmith API in Settings > API Keys
# Make sure API key env var is set:
# import os; os.environ["LANGSMITH_API_KEY"] = "<your-api-key>"
from langsmith import Client

client = Client()
helpfulness_prompt = client.pull_prompt("langchain-ai/rag-answer-helpfulness")

def check_helpfulness_grader(state: AgentState) -> str:
   
    # state에서 질문과 답변을 추출합니다
    query = state['query']
    answer = state['answer']

    # 답변의 유용성을 평가하기 위한 체인을 생성합니다
    helpfulness_chain = helpfulness_prompt | llm
    
    # 질문과 답변으로 체인을 호출합니다
    response = helpfulness_chain.invoke({'student_answer': answer, 'question': query})

    # 점수가 1이면 'helpful'을 반환하고, 그렇지 않으면 'unhelpful'을 반환합니다
    if response['Score'] == 1:
        return 'helpful'
    
    return 'unhelpful'

def check_helpfulness(state: AgentState) -> AgentState:
   
    # 이 함수는 현재 아무 작업도 수행하지 않으며 state를 그대로 반환합니다
    return state
graph_builder.add_node('retrieve', retrieve)
graph_builder.add_node('generate', generate)
graph_builder.add_node('rewrite', rewrite)
graph_builder.add_node('check_helpfulness', check_helpfulness)
from langgraph.graph import START, END

graph_builder.add_edge(START, 'retrieve')
graph_builder.add_conditional_edges(
    'retrieve',
    check_doc_relevance, 
    {
        'relevant': 'generate',
        'irrelevant': END
    }
)
graph_builder.add_conditional_edges(
    'generate',
    check_hallucination,
    {
        'not hallucinated': 'check_helpfulness',
        'hallucinated': 'generate'
    }
)

graph_builder.add_conditional_edges(
    'check_helpfulness',
    check_helpfulness_grader,
    {
        'helpful': END,
        'unhelpful': 'rewrite'
    }
)
graph_builder.add_edge('rewrite', 'retrieve')
graph = graph_builder.compile()
from IPython.display import Image, display

display(Image(graph.get_graph().draw_mermaid_png()))
initial_state = {'query': '연봉 5천만원인 거주자가 납부해야 하는 소득세는 얼마인가요?'}
graph.invoke(initial_state)
response: 0
{'query': '연봉 5천만원인 거주자가 납부해야 하는 소득세는 얼마인가요?',
 'context': [Document(id='a6c9015f-092f-4aa3-819b-e23588bb0a0f', metadata={'source': './tax_combined_final.txt'}, page_content='소득세법\n하는 자(제119조제9호에 따른 국내원천 부동산등양도소득을 지급하는 거주자 및 비거주자는 제외한다)는 제127조\n에도 불구하고 그 소득을 지급할 때에 다음 각 호의 금액을 그 비거주자의 국내원천소득에 대한 소득세로서 원천징\n수하여 그 원천징수한 날이 속하는 달의 다음 달 10일까지 대통령령으로 정하는 바에 따라 원천징수 관할 세무서,......


1.질문을 정확하게 LLM이 정확하게 리트리버할수 있게 질문을 한상태인데
판단결과가 irrelevant가 나왔고 엣지에서 질문과 리트리버된 문서와 의 관련도가 없으면 바로 END 되버리는 상황입니다.

정확하게 질문을 했는데 왜 결과가 나오지 않는지 잘 모르겠습니다!

제이쓴님의 프로필 이미지
제이쓴
지식공유자

안녕하세요! 다시 올려주신 부분에서 context를 보니 필요한 문서가 제대로 가져와지지 않는 것 같습니다. 데이터 전처리가 잘못되어서 문서를 가져오지 못하는 것 같은데요. 제공해주신 코드를 제가 설정한 vector db를 사용해서 검색하면 아래와 같은 결과가 나옵니다.

Screenshot 2026-01-30 at 9.37.13 AM.png

 

가져온 문서는 아래와 같습니다

{'query': '연봉 5천만원인 거주자가 납부해야 하는 소득세는 얼마인가요?',
 'context': [Document(metadata={'source': './documents/income_tax.txt'}, page_content='5년 이하: 30만원 → 납입연수\n5년 초과 10년 이하: 150만원 + 50만원 × (납입연수 - 5년)\n10년 초과 20년 이하: 400만원 + 80만원 × (납입연수 - 10년)\n20년 초과: 1,200만원 + 120만원 × (납입연수 - 20년)\n\n② 직장근로자 초과관세환급금을 분배하여 지급받는 경우 세액의 계산 방법 등 필요한 사항은 대통령령으로 정한다. \n<대통령 2014. 12. 23.>\n<전문개정 2009. 12. 31.>\n제64조(부동산매매업자에 대한 세액 계산의 특례)\n1) 대통령령으로 정하는 부동산매매업(이하 “부동산매매업”이라 한다)을 영위하는 거주자가 이하 “부동산매매업자”라 한다)로서 종합소득세법에 제104조제1항제1호(본항을 포함한다) 및 제103조 또는 같은 조 제7항 각 호의 어느 하나에 해당하는 자산의 매매자익(이하 “주택등매매차익”이라 한다)이 있는 자의 종합소득 산출세액은 다음 각 호의 세액 중 많은 것으로 한다. \n<개정 2014. 12. 23., 2017. 12. 19., 2020. 12. 29.>\n1. 종합소득 산출세액\n\n다음 각 목에 따른 세액의 합계액\n  가. 주택등매매차익에 따른 세율을 적용하여 산출한 세액의 합계액\n  나. 종합소득세법 제55조에 따라 주택등매매차익에 대한 개별세액을 공제한 금액을 과세표준으로 하고 이에 제55조에 따른 세율을 적용하여 산출세액'),
  Document(metadata={'source': './documents/income_tax.txt'}, page_content='② 제70조제1항, 제70조의2에 따른 제74조에 따라 차례로 할 것이 제70조제1항제2호에 따르며 서류를 제출하여야 한다는 경우에는 기준소득 중 거주자 본인이 된다(분산)과 제70조제2와 제74조에 따른 제료 및 제대법을 포함한다. 단, 차별제표청정인 그 업체를 남겨 제출한 경우로 그에 대하여 아니하다.<개정 2013. 1. 1.>\n  ③ 제80조에 따른 수익과 관련의 경우에는 기초공제 중 거주자 본인이 된다(분산)과 그에 관한 적지사항을 분명히 한다.\n[전문개정 2009. 12. 31.]\n[제목개정 2014. 1. 1.]\n제54조의2(공동사업에 대한 소득공제 등 특례) 제51조의3 또는 「조세특례제한법」에 따른 소득공제를 적용하거나 제59조의2에 따른 세액감면을 적용하는 경우 제54조제3항에 따라 공동사업자의 소득에 합산과세되는 특별세액거래의 지출․납입․투자 등의 금액이 있을 경우 주된 공동사업자의 소득에 합산과세되는 소득금액에 합산되어 주된 공동사업자의 합산과세세액은 공동사업소득액 또는 공동사업창출세액을 계산할 때 소득공제 또는 세액공제를 받을 수 있다. \n[개정 2014. 1. 1.]\n[전문개정 2009. 12. 31.]\n[제목개정 2014. 1. 1.]\n제2절 세액의 계산 <개정 2009. 12. 31.>\n제1관 세율 <개정 2009. 12. 31.>\n제55조(세율) 거주자의 종합소득에 대한 소득세는 해당 연도의 종합소득과세표준에 다음의 세율을 적용하여 계산한 금액(이하 "종합소득과세표준세액"이라 한다)을 그 세액으로 한다. <개정 2014. 1. 1., 2016. 12. 20., 2017. 12. 19., 2020. 12. 29., 2022. 12. 31.>\n종합소득\n┌───────────────┐\n│ 과세표준의 6개 구간 │\n├───────────────┤\n│ 1,400만원 이하        │ 84만원 + (1,400만원을 초과하는 금액의 15%)  │\n│ 1,400만원 초과        │ 84만원 + (5,000만원을 초과하는 금액의 24%)  │\n│ 8,800만원 이하        │ 624만원 + (5,000만원을 초과하는 금액의 24%)  │\n│ 8,800만원 초과        │ 1,536만원 + (8,800만원을 초과하는 금액의 35%)  │\n│ 1.5억원 초과          │ 4,046만원 + (1,500만원을 초과하는 금액의 38%)  │\n│ 3억원 초과            │ 6,460만원 + (3억원을 초과하는 금액의 40%)  │\n│ 5억원 초과            │ 14,760만원 + (5억원을 초과하는 금액의 42%)  │\n│ 10억원 초과           │ 38,406만원 + (10억원을 초과하는 금액의 45%)  │\n└───────────────┘\n② 거주자의 퇴직소득에 대한 소득세는 다음 각 호의 순서에 따라 계산한 금액(이하 ‘퇴직소득 산출세액’이라 한다)으로 한다. <개정 2013. 1. 1., 2014. 12. 23.>'),
  Document(metadata={'source': './documents/income_tax.txt'}, page_content='근무기한: 개월 수로 계산한다. 이 경우 1개월 미만의 기간이 있는 경우에는 이를 1개월로 본다.\n종합소득: 봉급ㆍ상여 등 제20조제1항제1호 및 제23조에 따른 포괄소득(제12조전단에 따른 비과세소득은 제외한다)을 한다.\n⑤ 삭제<2013. 1. 1.>\n⑥ 퇴직소득의 범위 및 계산방법과 그 밖에 필요한 사항은 대통령령으로 정한다.<전문개정 2009. 12. 31.>\n\n제23조 <삭제 2006. 12. 30.>\n제3절 소득금액의 계산\n제5장 2009. 12. 31.>\n제1항 총수입금액 <개정 2009. 12. 31.>\n제24조(총수입금액의 계산) ① 거주자 각 소득에 대한 총수입액(총급여액과 총연금액을 포함한다. 이하 같다)은 해당 과세기간에 수입하였거나 수입할 금액의 합계액으로 한다.\n② 제1항의 경우 금지 외의 자원을 수입할 때는 그 수입금액을 그 거래 당시의 가격에 의하여 계산한다.\n총수입액을 계산할 때 수입하였거나 수입할 금액의 범위와 계산에 관한 필요한 사항은 대통령령으로 정한다.<전문개정 2009. 12. 31.>\n제25조(총수입금액의 계산과 특례) ① 거주자가 부동산 또는 그 부동산상의 권리 등을 대여하거나 보증금ㆍ전세금 또는 이와 유사한 성질의 금액(이하 "보증금"이라 한다)을 받는 경우에는 대통령령으로 정하는 범에 따라 계산한 금액을 상속소득금액에산입할 때 총수입금액에 산입한다. 다만, 주택자금의 용도로 보이는 명저 1조(F) 또는 1세대당 3천미터 이하인 주택으로서 해당 세기기간이 기준시가가 2억인 이하 주택을 2026년 12월 31일까지 주택 수입 계산하지 아니하는 대여요금받는 경우에는 다음 중 하나의 하나에 해당하는 경우를 하며, 주택 수의 계산에 필요한 사항은 대통령령으로 정한다. <개정 2012. 1. 1., 2014. 1. 1., 2016. 12. 20, 2018. 12. 31, 2021. 12. 8, 2023. 12. 31.>\n\n3주택 이상을 소유하며 해당 주택의 보증금의 합계액이 3억원을 초과하는 경우  \n주택당(해당 주택의 기준시가가 12억원 이하인 주택 주택에 포함하지 아니하는 것을 소유하거나 해당 주택의 보증금의 합계액이 3억원 이상인 경우로서 대통령령으로 정하는 금액을 초과하는 경우\n거주자가 재산소득(資産所得) 또는 임금을 가사로 소비하지증명된 날에 지급받는 경우에 이를 소비하지 않고 사용하는 금액에 대해 가액에 해당하는 금액을 소비하지 않으면 소득금액에 대한 선입법의 사업소득금액에 산입한다.<삭제 2021. 12. 19.>\n[시행일: 2026. 1. 1. <지방소득세법 제25조제1항제1호>]')],
 'answer': AIMessage(content='연봉 5천만 원인 거주자의 소득세는 624만 원입니다. 소득세율은 5천만 원을 초과하는 금액에 대해 24%가 적용됩니다. 5천만 원 이하 구간의 세율은 이미 포함된 금액입니다.')}
Stanley Choi님의 프로필 이미지
Stanley Choi

작성한 질문수

질문하기