from typing import TypedDict, Listfrom langchain_core.documents import Documentclass FlareState(TypedDict): question: str partial_answer: str # 현재까지 생성된 답변 current_sentence: str # 현재 생성 중인 문장 is_low_confidence: bool # 저확률 토큰 포함 여부 documents: List[Document] # 검색된 문서 generation: str # 최종 답변 iteration: int # 반복 횟수
from langchain.chat_models import init_chat_modelfrom langchain_openai import OpenAIEmbeddingsfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.output_parsers import StrOutputParserllm = init_chat_model("gpt-4o-mini", temperature=0)embeddings = OpenAIEmbeddings()def generate_next_sentence(state: FlareState) -> FlareState: """다음 문장을 생성합니다.""" question = state["question"] partial = state.get("partial_answer", "") docs = state.get("documents", []) iteration = state.get("iteration", 0) context = "\n\n".join(doc.page_content for doc in docs) if docs else "" prompt = ChatPromptTemplate.from_messages([ ("system", "질문에 대한 답변을 이어서 작성하세요. " "정확히 1~2문장만 생성하세요.\n\n" "컨텍스트:\n{context}"), ("human", "질문: {question}\n\n" "현재까지의 답변: {partial}\n\n" "다음 1~2문장을 이어서 작성하세요. " "더 이상 쓸 내용이 없으면 [완료]를 출력하세요."), ]) chain = prompt | llm | StrOutputParser() sentence = chain.invoke({ "context": context, "question": question, "partial": partial, }) return { "current_sentence": sentence.strip(), "iteration": iteration + 1, }
Copy
from langchain.chat_models import init_chat_modelfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.output_parsers import StrOutputParserconfidence_llm = init_chat_model("gpt-4o-mini", temperature=0)def check_confidence(state: FlareState) -> FlareState: """생성된 문장의 확신도를 평가합니다.""" question = state["question"] sentence = state["current_sentence"] # 원 FLARE 논문은 토큰별 logprob 확률로 확신도를 판단합니다. # 아래는 FLARE-inspired 구현으로, API 제한 환경에서 # LLM 자기 평가로 대체한 간소화 버전입니다. prompt = ChatPromptTemplate.from_messages([ ("system", "다음 문장이 질문에 대해 사실에 기반한 확신 있는 답변인지 평가하세요.\n" "추측이나 불확실한 내용이 포함되어 있으면 'low'를 출력하세요.\n" "확신 있는 사실적 내용이면 'high'를 출력하세요.\n" "'low' 또는 'high'만 출력하세요."), ("human", "질문: {question}\n문장: {sentence}"), ]) chain = prompt | confidence_llm | StrOutputParser() result = chain.invoke({"question": question, "sentence": sentence}) is_low = "low" in result.strip().lower() return {"is_low_confidence": is_low}
Copy
from langchain_chroma import Chromavectorstore = Chroma(collection_name="documents", embedding_function=embeddings)retriever = vectorstore.as_retriever(search_kwargs={"k": 3})def active_retrieve(state: FlareState) -> FlareState: """불확실한 문장을 쿼리로 사용하여 검색합니다.""" # 임시 생성 문장 자체를 검색 쿼리로 활용 query = state["current_sentence"] documents = retriever.invoke(query) return {"documents": documents}
Copy
def regenerate_sentence(state: FlareState) -> FlareState: """검색 결과를 참고하여 문장을 재생성합니다.""" question = state["question"] partial = state.get("partial_answer", "") docs = state.get("documents", []) context = "\n\n".join(doc.page_content for doc in docs) prompt = ChatPromptTemplate.from_messages([ ("system", "다음 컨텍스트를 참고하여 질문에 대한 답변을 이어서 작성하세요. " "정확히 1~2문장만 생성하세요. " "컨텍스트에 근거한 사실만 작성하세요.\n\n" "컨텍스트:\n{context}"), ("human", "질문: {question}\n\n" "현재까지의 답변: {partial}\n\n" "다음 1~2문장을 이어서 작성하세요."), ]) chain = prompt | llm | StrOutputParser() sentence = chain.invoke({ "context": context, "question": question, "partial": partial, }) return {"current_sentence": sentence.strip()}
Copy
def confirm_sentence(state: FlareState) -> FlareState: """확정된 문장을 부분 답변에 추가합니다.""" partial = state.get("partial_answer", "") sentence = state["current_sentence"] if "[완료]" in sentence: return {"generation": partial.strip()} updated = f"{partial} {sentence}".strip() return {"partial_answer": updated}