基础RAG实现,最佳入门选择(十一)

RAG中的反馈循环

实现一个带有反馈循环机制的RAG系统,该机制随着时间的推移不断改进。通过收集和整合用户反馈,系统学会了在每次交互中提供更相关和更高质量的响应。传统的RAG系统是静态的——它们仅基于嵌入相似性来检索信息。通过反馈循环,创建了一个动态系统:

-记住什么有效(什么无效)
-随着时间的推移调整文档相关性分数
-将成功的问答配对纳入其知识库
-通过每次用户交互变得更智能

具体代码实现

PDF文本提取

从PDF文件中提取全部文本

def extract_text_from_pdf(pdf_path):
    """
    从PDF文件中提取全部文本
    :param pdf_path: PDF文件路径
    :return: 提取的文本内容(str)
    """
    print(f"[步骤] 正在从PDF提取文本: {pdf_path}")
    with open(pdf_path, 'rb') as f:
        reader = PdfReader(f)
        text = ""
        for i, page in enumerate(reader.pages):
            page_text = page.extract_text()
            if page_text:
                text += page_text
            print(f"  - 已提取第{i+1}页")
    print(f"[完成] PDF文本提取完成,总长度: {len(text)} 字符\n")
    return text

文本分块

文本分割为带重叠的块

def chunk_text(text, n=1000, overlap=200):
    """
    将文本分割为带重叠的块
    :param text: 原始文本
    :param n: 每块字符数
    :param overlap: 块间重叠字符数
    :return: 文本块列表
    """
    print(f"[分块] 每块{n}字符,重叠{overlap}字符")
    chunks = []
    for i in range(0, len(text), n - overlap):
        chunks.append(text[i:i + n])
    print(f"[分块] 完成,共{len(chunks)}块\n")
    return chunks

向量生成

阿里embedding模型批量生成文本向量

def create_embeddings(texts, model=EMBEDDING_MODEL):
    """
    用阿里embedding模型批量生成文本向量
    :param texts: 文本列表
    :param model: 嵌入模型名
    :return: 向量列表
    """
    if isinstance(texts, str):
        texts = [texts]
    print(f"[嵌入生成] 正在生成{len(texts)}条文本的向量...")
    try:
        response = TextEmbedding.call(
            model=model,
            input=texts,
            api_key=ALI_API_KEY
        )
        if response.status_code == 200:
            embeddings = [np.array(item['embedding']) for item in response.output['embeddings']]
            print(f"[嵌入生成] 成功,返回{len(embeddings)}条向量\n")
            return embeddings if len(embeddings) > 1 else embeddings[0]
        else:
            print(f"[嵌入生成] 失败: {response.message}")
            return [np.zeros(1536)] * len(texts)
    except Exception as e:
        print(f"[嵌入生成] 异常: {e}")
        return [np.zeros(1536)] * len(texts)

简单向量库

简单的向量存储与检索类,支持相似度检索和元数据管理

class SimpleVectorStore:
    """
    简单的向量存储与检索类,支持相似度检索和元数据管理
    """
    def __init__(self):
        self.vectors = []
        self.texts = []
        self.metadata = []

    def add_item(self, text, embedding, metadata=None):
        self.vectors.append(np.array(embedding))
        self.texts.append(text)
        self.metadata.append(metadata or {})

    def similarity_search(self, query_embedding, k=5, filter_func=None):
        if not self.vectors:
            return []
        query_vector = np.array(query_embedding)
        similarities = []
        for i, vector in enumerate(self.vectors):
            if filter_func and not filter_func(self.metadata[i]):
                continue
            sim = np.dot(query_vector, vector) / (np.linalg.norm(query_vector) * np.linalg.norm(vector))
            similarities.append((i, sim))
        similarities.sort(key=lambda x: x[1], reverse=True)
        results = []
        for i in range(min(k, len(similarities))):
            idx, score = similarities[i]
            results.append({
                "text": self.texts[idx],
                "metadata": self.metadata[idx],
                "similarity": score,
                "relevance_score": self.metadata[idx].get("relevance_score", score)
            })
        return results

反馈数据处理

格式化用户反馈为字典

def get_user_feedback(query, response, relevance, quality, comments=""):
    """
    格式化用户反馈为字典
    """
    return {
        "query": query,
        "response": response,
        "relevance": int(relevance),
        "quality": int(quality),
        "comments": comments,
        "timestamp": datetime.now().isoformat()
    }

存储反馈到JSON文件

def store_feedback(feedback, feedback_file="feedback_data.json"):
    """
    存储反馈到JSON文件
    """
    with open(feedback_file, "a", encoding="utf-8") as f:
        json.dump(feedback, f, ensure_ascii=False)
        f.write("\n")

加载历史反馈数据

def load_feedback_data(feedback_file="feedback_data.json"):
    """
    加载历史反馈数据
    """
    feedback_data = []
    try:
        with open(feedback_file, "r", encoding="utf-8") as f:
            for line in f:
                if line.strip():
                    feedback_data.append(json.loads(line.strip()))
    except FileNotFoundError:
        print("[提示] 未找到反馈数据文件,初始为空。")
    return feedback_data

文档处理主流程

处理PDF文档,提取文本、分块、生成向量并构建向量库

def process_document(pdf_path, chunk_size=1000, chunk_overlap=200):
    """
    处理PDF文档,提取文本、分块、生成向量并构建向量库。
    """
    print("[主流程] 开始处理文档...")
    extracted_text = extract_text_from_pdf(pdf_path)
    text_chunks = chunk_text(extracted_text, chunk_size, chunk_overlap)
    print("[主流程] 初始化向量库...")
    vector_store = SimpleVectorStore()
    print("[主流程] 为每个块生成向量...")
    chunk_embeddings = create_embeddings(text_chunks)
    for i, (chunk, embedding) in enumerate(zip(text_chunks, chunk_embeddings)):
        print(f"[块{i+1}/{len(text_chunks)}] 已生成向量,长度: {len(chunk)} 字符")
        vector_store.add_item(chunk, embedding, {
            "type": "chunk",
            "index": i,
            "source": pdf_path,
            "relevance_score": 1.0,
            "feedback_count": 0
        })
    print("[主流程] 文档处理完毕,向量库构建完成\n")
    return text_chunks, vector_store

反馈相关辅助

用LLM判断历史反馈是否与当前query和文档相关

def assess_feedback_relevance(query, doc_text, feedback):
    """
    用LLM判断历史反馈是否与当前query和文档相关
    """
    system_prompt = "你是相关性判断专家,只需判断历史反馈是否对当前问题和文档有用,只回答yes或no。"
    user_prompt = f"""
当前问题: {query}
历史反馈问题: {feedback['query']}
文档内容: {doc_text[:500]}... [截断]
历史反馈回答: {feedback['response'][:500]}... [截断]
该反馈对当前问题和文档是否相关? (yes/no)
"""
    try:
        response = Generation.call(
            model=LLM_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt}
            ],
            api_key=ALI_API_KEY,
            result_format='message'
        )
        if response.status_code == 200:
            answer = response.output.choices[0].message.content.strip().lower()
            return 'yes' in answer
        else:
            print(f"[反馈相关性] LLM调用失败: {response.message}")
            return False
    except Exception as e:
        print(f"[反馈相关性] LLM调用异常: {e}")
        return False

根据历史反馈动态调整检索结果的相关性分数

def adjust_relevance_scores(query, results, feedback_data):
    """
    根据历史反馈动态调整检索结果的相关性分数
    """
    if not feedback_data:
        return results
    print("[反馈增强] 正在根据历史反馈调整相关性分数...")
    for i, result in enumerate(results):
        document_text = result["text"]
        relevant_feedback = []
        for feedback in feedback_data:
            is_relevant = assess_feedback_relevance(query, document_text, feedback)
            if is_relevant:
                relevant_feedback.append(feedback)
        if relevant_feedback:
            avg_relevance = sum(f['relevance'] for f in relevant_feedback) / len(relevant_feedback)
            modifier = 0.5 + (avg_relevance / 5.0)
            original_score = result["similarity"]
            adjusted_score = original_score * modifier
            result["original_similarity"] = original_score
            result["similarity"] = adjusted_score
            result["relevance_score"] = adjusted_score
            result["feedback_applied"] = True
            result["feedback_count"] = len(relevant_feedback)
            print(f"  文档{i+1}: 分数由{original_score:.4f}调整为{adjusted_score:.4f},基于{len(relevant_feedback)}条反馈")
    results.sort(key=lambda x: x["similarity"], reverse=True)
    return results

反馈增强索引

用高质量反馈增强向量库

def fine_tune_index(current_store, chunks, feedback_data):
    """
    用高质量反馈增强向量库
    """
    print("[增强索引] 正在用高质量反馈增强向量库...")
    good_feedback = [f for f in feedback_data if f['relevance'] >= 4 and f['quality'] >= 4]
    if not good_feedback:
        print("[增强索引] 未找到高质量反馈,跳过增强。")
        return current_store
    new_store = SimpleVectorStore()
    for i in range(len(current_store.texts)):
        new_store.add_item(
            text=current_store.texts[i],
            embedding=current_store.vectors[i],
            metadata=current_store.metadata[i].copy()
        )
    for feedback in good_feedback:
        enhanced_text = f"问题: {feedback['query']}\n回答: {feedback['response']}"
        embedding = create_embeddings(enhanced_text)
        new_store.add_item(
            text=enhanced_text,
            embedding=embedding,
            metadata={
                "type": "feedback_enhanced",
                "query": feedback["query"],
                "relevance_score": 1.2,
                "feedback_count": 1,
                "original_feedback": feedback
            }
        )
        print(f"  已添加增强内容: {feedback['query'][:30]}...")
    print(f"[增强索引] 增强后总条数: {len(new_store.texts)} (原始: {len(chunks)})\n")
    return new_store

LLM生成回答

用大模型基于上下文生成回答

def generate_response(query, context, model=LLM_MODEL):
    """
    用大模型基于上下文生成回答
    """
    print("[流程] 正在调用大模型生成最终回答...")
    system_prompt = "你是一个AI助手,只能基于给定上下文回答问题。如果上下文无法直接回答,请回复:'信息不足,无法回答。'"
    user_prompt = f"""
上下文:\n{context}\n\n问题:{query}\n\n请只基于上述上下文简明准确作答。"""
    try:
        response = Generation.call(
            model=model,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt}
            ],
            api_key=ALI_API_KEY,
            result_format='message'
        )
        if response.status_code == 200:
            print("[流程] 回答生成成功\n")
            return response.output.choices[0].message.content.strip()
        else:
            print(f"[流程] 回答生成失败: {response.message}")
            return ""
    except Exception as e:
        print(f"[流程] 回答生成异常: {e}")
        return ""

Feedback Loop RAG主流程

完整RAG流程,集成反馈闭环

def rag_with_feedback_loop(query, vector_store, feedback_data, k=5, model=LLM_MODEL):
    """
    完整RAG流程,集成反馈闭环
    """
    print(f"\n=== [RAG流程] 处理问题: {query} ===")
    query_embedding = create_embeddings(query)
    results = vector_store.similarity_search(query_embedding, k=k)
    adjusted_results = adjust_relevance_scores(query, results, feedback_data)
    retrieved_texts = [result["text"] for result in adjusted_results]
    context = "\n\n---\n\n".join(retrieved_texts)
    print("[流程] 正在生成回答...")
    response = generate_response(query, context, model)
    print("\n=== [RAG流程] 最终回答 ===")
    print(response)
    return {
        "query": query,
        "retrieved_documents": adjusted_results,
        "response": response
    }

完整工作流

执行完整RAG+反馈闭环流程

def full_rag_workflow(pdf_path, query, feedback_data=None, feedback_file="feedback_data.json", fine_tune=False):
    """
    执行完整RAG+反馈闭环流程
    """
    if feedback_data is None:
        feedback_data = load_feedback_data(feedback_file)
        print(f"[反馈] 已加载{len(feedback_data)}条历史反馈\n")
    chunks, vector_store = process_document(pdf_path)
    if fine_tune and feedback_data:
        vector_store = fine_tune_index(vector_store, chunks, feedback_data)
    result = rag_with_feedback_loop(query, vector_store, feedback_data)
    print("\n=== [反馈收集] 请对本次回答进行评价 ===")
    relevance = input("相关性评分(1-5): ")
    quality = input("质量评分(1-5): ")
    comments = input("其他建议(可选): ")
    feedback = get_user_feedback(
        query=query,
        response=result["response"],
        relevance=int(relevance),
        quality=int(quality),
        comments=comments
    )
    store_feedback(feedback, feedback_file)
    print("[反馈] 已记录,感谢您的参与!\n")
    return result

附录

执行效果

========== Feedback Loop RAG主流程演示 ==========
[配置] 使用API密钥: sk-fc6ad...2f23
[配置] PDF路径: data/2888年Java程序员找工作最新场景题.pdf
[配置] 问题: Java程序员面试中常见的技术问题有哪些?
[配置] 反馈增强: True

[反馈] 已加载1条历史反馈

[主流程] 开始处理文档...
[步骤] 正在从PDF提取文本: data/2888年Java程序员找工作最新场景题.pdf
  - 已提取第1页
  - 已提取第2页
  - 已提取第3页
  - 已提取第4页
  - 已提取第5页
  - 已提取第6页
  - 已提取第7页
  - 已提取第8页
  - 已提取第9页
  - 已提取第10页
[完成] PDF文本提取完成,总长度: 6984 字符

[分块] 每块1000字符,重叠200字符
[分块] 完成,共9块

[主流程] 初始化向量库...
[主流程] 为每个块生成向量...
[嵌入生成] 正在生成9条文本的向量...
[嵌入生成] 成功,返回9条向量

[块1/9] 已生成向量,长度: 1000 字符
[块2/9] 已生成向量,长度: 1000 字符
[块3/9] 已生成向量,长度: 1000 字符
[块4/9] 已生成向量,长度: 1000 字符
[块5/9] 已生成向量,长度: 1000 字符
[块6/9] 已生成向量,长度: 1000 字符
[块7/9] 已生成向量,长度: 1000 字符
[块8/9] 已生成向量,长度: 1000 字符
[块9/9] 已生成向量,长度: 584 字符
[主流程] 文档处理完毕,向量库构建完成

[增强索引] 正在用高质量反馈增强向量库...
[增强索引] 未找到高质量反馈,跳过增强。

=== [RAG流程] 处理问题: Java程序员面试中常见的技术问题有哪些? ===
[嵌入生成] 正在生成1条文本的向量...
[嵌入生成] 成功,返回1条向量

[反馈增强] 正在根据历史反馈调整相关性分数...
  文档1: 分数由0.6383调整为0.5745,基于1条反馈
  文档2: 分数由0.5401调整为0.4861,基于1条反馈
  文档3: 分数由0.4255调整为0.3829,基于1条反馈
  文档4: 分数由0.3958调整为0.3562,基于1条反馈
  文档5: 分数由0.3343调整为0.3009,基于1条反馈
[流程] 正在生成回答...
[流程] 正在调用大模型生成最终回答...
[流程] 回答生成成功


=== [RAG流程] 最终回答 ===
根据上下文,Java程序员面试中常见的技术问题包括但不限于:

- 并发编程、NIO、JVM等进阶知识。
- Spring、Netty等流行框架的基本原理。
- 对CAP原则、微服务架构、弹性设计以及Spring Cloud、CloudNative等相关技术框架的理解。
- TCP/IP协议的三次握手、四次挥手过程,Socket编程基础,以及select、poll、epoll等I/O多路复用技术的知识点。

=== [反馈收集] 请对本次回答进行评价 ===
相关性评分(1-5): 2
质量评分(1-5): 2
其他建议(可选): 一定要加上AI的知识
[反馈] 已记录,感谢您的参与!

========== 演示结束 ==========

进程已结束,退出代码为 0

feedback_data.json 历史问答记录展示

{"query": "Java程序员面试中常见的技术问题有哪些?", "response": "根据上下文,Java程序员面试中常见的技术问题涉及以下几个方面:\n\n- 并发编程、NIO、JVM等进阶知识。\n- Spring、Netty等流行框架的基本原理。\n- 分布式架构相关的CAP原则、微服务架构、弹性设计以及Spring Cloud、Cloud Native等相关技术框架。\n- 网络编程技能,包括TCP/IP协议的三次握手、四次挥手过程,Socket编程基础,以及select、poll、epoll等I/O多路复用技术。", "relevance": 2, "quality": 2, "comments": "回答需要结合AI方面的知识", "timestamp": "2025-06-25T17:45:46.310644"}
{"query": "Java程序员面试中常见的技术问题有哪些?", "response": "根据上下文,Java程序员面试中常见的技术问题包括但不限于:\n\n- 并发编程、NIO、JVM等进阶知识。\n- Spring、Netty等流行框架的基本原理。\n- 对CAP原则、微服务架构、弹性设计以及Spring Cloud、CloudNative等相关技术框架的理解。\n- TCP/IP协议的三次握手、四次挥手过程,Socket编程基础,以及select、poll、epoll等I/O多路复用技术的知识点。", "relevance": 2, "quality": 2, "comments": "一定要加上AI的知识", "timestamp": "2025-06-25T17:48:17.234474"}

完整代码示例

# -*- coding: utf-8 -*-
"""
基于阿里大模型的反馈闭环RAG主流程(详细中文注释+详细控制台输出)
"""
import os
import sys
import numpy as np
import json
from PyPDF2 import PdfReader
from dashscope import Generation, TextEmbedding
from datetime import datetime

# ========== 密钥配置:优先从test/api_keys.py读取,否则用环境变量 ==========
# try:
#     sys.path.append('test')
#     from api_keys import ALI_API_KEY
# except Exception:
#     ALI_API_KEY = os.getenv("ALI_API_KEY", "")
#     if not ALI_API_KEY:
#         print("[错误] 未找到API密钥,请在test/api_keys.py或环境变量中配置ALI_API_KEY!")
#         sys.exit(1)
ALI_API_KEY="sk-fc6ad8ecccc5225372f23"
# ==============================================

LLM_MODEL = "qwen-max"  # 通义千问主力模型
EMBEDDING_MODEL = "text-embedding-v2"  # 阿里云嵌入模型

# ========== PDF文本提取 ==========
def extract_text_from_pdf(pdf_path):
    """
    从PDF文件中提取全部文本
    :param pdf_path: PDF文件路径
    :return: 提取的文本内容(str)
    """
    print(f"[步骤] 正在从PDF提取文本: {pdf_path}")
    with open(pdf_path, 'rb') as f:
        reader = PdfReader(f)
        text = ""
        for i, page in enumerate(reader.pages):
            page_text = page.extract_text()
            if page_text:
                text += page_text
            print(f"  - 已提取第{i+1}页")
    print(f"[完成] PDF文本提取完成,总长度: {len(text)} 字符\n")
    return text

# ========== 文本分块 ==========
def chunk_text(text, n=1000, overlap=200):
    """
    将文本分割为带重叠的块
    :param text: 原始文本
    :param n: 每块字符数
    :param overlap: 块间重叠字符数
    :return: 文本块列表
    """
    print(f"[分块] 每块{n}字符,重叠{overlap}字符")
    chunks = []
    for i in range(0, len(text), n - overlap):
        chunks.append(text[i:i + n])
    print(f"[分块] 完成,共{len(chunks)}块\n")
    return chunks

# ========== 向量生成 ==========
def create_embeddings(texts, model=EMBEDDING_MODEL):
    """
    用阿里embedding模型批量生成文本向量
    :param texts: 文本列表
    :param model: 嵌入模型名
    :return: 向量列表
    """
    if isinstance(texts, str):
        texts = [texts]
    print(f"[嵌入生成] 正在生成{len(texts)}条文本的向量...")
    try:
        response = TextEmbedding.call(
            model=model,
            input=texts,
            api_key=ALI_API_KEY
        )
        if response.status_code == 200:
            embeddings = [np.array(item['embedding']) for item in response.output['embeddings']]
            print(f"[嵌入生成] 成功,返回{len(embeddings)}条向量\n")
            return embeddings if len(embeddings) > 1 else embeddings[0]
        else:
            print(f"[嵌入生成] 失败: {response.message}")
            return [np.zeros(1536)] * len(texts)
    except Exception as e:
        print(f"[嵌入生成] 异常: {e}")
        return [np.zeros(1536)] * len(texts)

# ========== 简单向量库 ==========
class SimpleVectorStore:
    """
    简单的向量存储与检索类,支持相似度检索和元数据管理
    """
    def __init__(self):
        self.vectors = []
        self.texts = []
        self.metadata = []

    def add_item(self, text, embedding, metadata=None):
        self.vectors.append(np.array(embedding))
        self.texts.append(text)
        self.metadata.append(metadata or {})

    def similarity_search(self, query_embedding, k=5, filter_func=None):
        if not self.vectors:
            return []
        query_vector = np.array(query_embedding)
        similarities = []
        for i, vector in enumerate(self.vectors):
            if filter_func and not filter_func(self.metadata[i]):
                continue
            sim = np.dot(query_vector, vector) / (np.linalg.norm(query_vector) * np.linalg.norm(vector))
            similarities.append((i, sim))
        similarities.sort(key=lambda x: x[1], reverse=True)
        results = []
        for i in range(min(k, len(similarities))):
            idx, score = similarities[i]
            results.append({
                "text": self.texts[idx],
                "metadata": self.metadata[idx],
                "similarity": score,
                "relevance_score": self.metadata[idx].get("relevance_score", score)
            })
        return results

# ========== 反馈数据处理 ==========
def get_user_feedback(query, response, relevance, quality, comments=""):
    """
    格式化用户反馈为字典
    """
    return {
        "query": query,
        "response": response,
        "relevance": int(relevance),
        "quality": int(quality),
        "comments": comments,
        "timestamp": datetime.now().isoformat()
    }

def store_feedback(feedback, feedback_file="feedback_data.json"):
    """
    存储反馈到JSON文件
    """
    with open(feedback_file, "a", encoding="utf-8") as f:
        json.dump(feedback, f, ensure_ascii=False)
        f.write("\n")

def load_feedback_data(feedback_file="feedback_data.json"):
    """
    加载历史反馈数据
    """
    feedback_data = []
    try:
        with open(feedback_file, "r", encoding="utf-8") as f:
            for line in f:
                if line.strip():
                    feedback_data.append(json.loads(line.strip()))
    except FileNotFoundError:
        print("[提示] 未找到反馈数据文件,初始为空。")
    return feedback_data

# ========== 文档处理主流程 ==========
def process_document(pdf_path, chunk_size=1000, chunk_overlap=200):
    """
    处理PDF文档,提取文本、分块、生成向量并构建向量库。
    """
    print("[主流程] 开始处理文档...")
    extracted_text = extract_text_from_pdf(pdf_path)
    text_chunks = chunk_text(extracted_text, chunk_size, chunk_overlap)
    print("[主流程] 初始化向量库...")
    vector_store = SimpleVectorStore()
    print("[主流程] 为每个块生成向量...")
    chunk_embeddings = create_embeddings(text_chunks)
    for i, (chunk, embedding) in enumerate(zip(text_chunks, chunk_embeddings)):
        print(f"[块{i+1}/{len(text_chunks)}] 已生成向量,长度: {len(chunk)} 字符")
        vector_store.add_item(chunk, embedding, {
            "type": "chunk",
            "index": i,
            "source": pdf_path,
            "relevance_score": 1.0,
            "feedback_count": 0
        })
    print("[主流程] 文档处理完毕,向量库构建完成\n")
    return text_chunks, vector_store

# ========== 反馈相关辅助 ==========
def assess_feedback_relevance(query, doc_text, feedback):
    """
    用LLM判断历史反馈是否与当前query和文档相关
    """
    system_prompt = "你是相关性判断专家,只需判断历史反馈是否对当前问题和文档有用,只回答yes或no。"
    user_prompt = f"""
当前问题: {query}
历史反馈问题: {feedback['query']}
文档内容: {doc_text[:500]}... [截断]
历史反馈回答: {feedback['response'][:500]}... [截断]
该反馈对当前问题和文档是否相关? (yes/no)
"""
    try:
        response = Generation.call(
            model=LLM_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt}
            ],
            api_key=ALI_API_KEY,
            result_format='message'
        )
        if response.status_code == 200:
            answer = response.output.choices[0].message.content.strip().lower()
            return 'yes' in answer
        else:
            print(f"[反馈相关性] LLM调用失败: {response.message}")
            return False
    except Exception as e:
        print(f"[反馈相关性] LLM调用异常: {e}")
        return False

def adjust_relevance_scores(query, results, feedback_data):
    """
    根据历史反馈动态调整检索结果的相关性分数
    """
    if not feedback_data:
        return results
    print("[反馈增强] 正在根据历史反馈调整相关性分数...")
    for i, result in enumerate(results):
        document_text = result["text"]
        relevant_feedback = []
        for feedback in feedback_data:
            is_relevant = assess_feedback_relevance(query, document_text, feedback)
            if is_relevant:
                relevant_feedback.append(feedback)
        if relevant_feedback:
            avg_relevance = sum(f['relevance'] for f in relevant_feedback) / len(relevant_feedback)
            modifier = 0.5 + (avg_relevance / 5.0)
            original_score = result["similarity"]
            adjusted_score = original_score * modifier
            result["original_similarity"] = original_score
            result["similarity"] = adjusted_score
            result["relevance_score"] = adjusted_score
            result["feedback_applied"] = True
            result["feedback_count"] = len(relevant_feedback)
            print(f"  文档{i+1}: 分数由{original_score:.4f}调整为{adjusted_score:.4f},基于{len(relevant_feedback)}条反馈")
    results.sort(key=lambda x: x["similarity"], reverse=True)
    return results

# ========== 反馈增强索引 ==========
def fine_tune_index(current_store, chunks, feedback_data):
    """
    用高质量反馈增强向量库
    """
    print("[增强索引] 正在用高质量反馈增强向量库...")
    good_feedback = [f for f in feedback_data if f['relevance'] >= 4 and f['quality'] >= 4]
    if not good_feedback:
        print("[增强索引] 未找到高质量反馈,跳过增强。")
        return current_store
    new_store = SimpleVectorStore()
    for i in range(len(current_store.texts)):
        new_store.add_item(
            text=current_store.texts[i],
            embedding=current_store.vectors[i],
            metadata=current_store.metadata[i].copy()
        )
    for feedback in good_feedback:
        enhanced_text = f"问题: {feedback['query']}\n回答: {feedback['response']}"
        embedding = create_embeddings(enhanced_text)
        new_store.add_item(
            text=enhanced_text,
            embedding=embedding,
            metadata={
                "type": "feedback_enhanced",
                "query": feedback["query"],
                "relevance_score": 1.2,
                "feedback_count": 1,
                "original_feedback": feedback
            }
        )
        print(f"  已添加增强内容: {feedback['query'][:30]}...")
    print(f"[增强索引] 增强后总条数: {len(new_store.texts)} (原始: {len(chunks)})\n")
    return new_store

# ========== LLM生成回答 ==========
def generate_response(query, context, model=LLM_MODEL):
    """
    用大模型基于上下文生成回答
    """
    print("[流程] 正在调用大模型生成最终回答...")
    system_prompt = "你是一个AI助手,只能基于给定上下文回答问题。如果上下文无法直接回答,请回复:'信息不足,无法回答。'"
    user_prompt = f"""
上下文:\n{context}\n\n问题:{query}\n\n请只基于上述上下文简明准确作答。"""
    try:
        response = Generation.call(
            model=model,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt}
            ],
            api_key=ALI_API_KEY,
            result_format='message'
        )
        if response.status_code == 200:
            print("[流程] 回答生成成功\n")
            return response.output.choices[0].message.content.strip()
        else:
            print(f"[流程] 回答生成失败: {response.message}")
            return ""
    except Exception as e:
        print(f"[流程] 回答生成异常: {e}")
        return ""

# ========== Feedback Loop RAG主流程 ==========
def rag_with_feedback_loop(query, vector_store, feedback_data, k=5, model=LLM_MODEL):
    """
    完整RAG流程,集成反馈闭环
    """
    print(f"\n=== [RAG流程] 处理问题: {query} ===")
    query_embedding = create_embeddings(query)
    results = vector_store.similarity_search(query_embedding, k=k)
    adjusted_results = adjust_relevance_scores(query, results, feedback_data)
    retrieved_texts = [result["text"] for result in adjusted_results]
    context = "\n\n---\n\n".join(retrieved_texts)
    print("[流程] 正在生成回答...")
    response = generate_response(query, context, model)
    print("\n=== [RAG流程] 最终回答 ===")
    print(response)
    return {
        "query": query,
        "retrieved_documents": adjusted_results,
        "response": response
    }

# ========== 完整工作流 ==========
def full_rag_workflow(pdf_path, query, feedback_data=None, feedback_file="feedback_data.json", fine_tune=False):
    """
    执行完整RAG+反馈闭环流程
    """
    if feedback_data is None:
        feedback_data = load_feedback_data(feedback_file)
        print(f"[反馈] 已加载{len(feedback_data)}条历史反馈\n")
    chunks, vector_store = process_document(pdf_path)
    if fine_tune and feedback_data:
        vector_store = fine_tune_index(vector_store, chunks, feedback_data)
    result = rag_with_feedback_loop(query, vector_store, feedback_data)
    print("\n=== [反馈收集] 请对本次回答进行评价 ===")
    relevance = input("相关性评分(1-5): ")
    quality = input("质量评分(1-5): ")
    comments = input("其他建议(可选): ")
    feedback = get_user_feedback(
        query=query,
        response=result["response"],
        relevance=int(relevance),
        quality=int(quality),
        comments=comments
    )
    store_feedback(feedback, feedback_file)
    print("[反馈] 已记录,感谢您的参与!\n")
    return result

# ========== main方法演示 ==========
def main():
    """
    主方法:体验Feedback Loop RAG
    """
    pdf_path = "data/2888年Java程序员找工作最新场景题.pdf"  # 可自定义
    query = "Java程序员面试中常见的技术问题有哪些?"  # 可自定义
    feedback_file = "feedback_data.json"
    fine_tune = True  # 是否用历史反馈增强索引
    print("\n========== Feedback Loop RAG主流程演示 ==========")
    print(f"[配置] 使用API密钥: {ALI_API_KEY[:8]}...{ALI_API_KEY[-4:]}")
    print(f"[配置] PDF路径: {pdf_path}")
    print(f"[配置] 问题: {query}")
    print(f"[配置] 反馈增强: {fine_tune}\n")
    full_rag_workflow(pdf_path, query, feedback_file=feedback_file, fine_tune=fine_tune)
    print("========== 演示结束 ==========")

if __name__ == "__main__":
    main()

posted @ 2025-06-25 18:02  舒一笑不秃头  阅读(15)  评论(0)    收藏  举报