用于 HR FAQ 场景的AI Agent原型演示

先看这个HR FAQ场景的Agent实现代码:

示例代码取自《AI Agent智能体开发实践》第8章。

复制代码
# -*- coding: utf-8 -*-
"""
Created on Sun Jul 27 21:05:36 2025

@author: liguo
"""
#1. 安装依赖(首次运行时启用)
# pip install langchain sentence-transformers faiss-cpu pypdf fastapi uvicorn pydantic dashscope
#2. 导入核心库
import os
import dashscope
from langchain_community.vectorstores import FAISS
from langchain.chains import RetrievalQA
from langchain.schema import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
from langchain.llms.base import LLM
from typing import Optional, List, Mapping, Any
from langchain.embeddings.base import Embeddings  # 导入LangChain嵌入基类

# --------------------------
# 1. 自定义阿里云DashScope嵌入模型(避免Hugging Face依赖)
# --------------------------
class DashScopeEmbeddings(Embeddings):
    def __init__(self, model_name: str = "text-embedding-v2"):
        self.model_name = model_name  # 阿里云嵌入模型,无需下载
    
    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        """为文档列表生成嵌入向量"""
        response = dashscope.TextEmbedding.call(
            model=self.model_name,
            input=texts
        )
        # 检查响应是否成功(修改部分)
        if hasattr(response, 'output') and 'embeddings' in response.output:
            return [item["embedding"] for item in response.output["embeddings"]]
        else:
            raise Exception(f"嵌入模型调用失败: {getattr(response, 'message', '未知错误')}")
    
    def embed_query(self, text: str) -> List[float]:
        """为查询文本生成嵌入向量"""
        return self.embed_documents([text])[0]

# --------------------------
# 2. 自定义阿里云DashScope LLM(兼容旧版本LangChain)
# --------------------------
class DashScopeLLM(LLM):
    model_name: str = "qwen-plus"  # Qwen模型版本
    temperature: float = 0.3
    max_tokens: int = 512
    
    @property
    def _llm_type(self) -> str:
        return "dashscope"
    
    def _call(
        self,
        prompt: str,
        stop: Optional[List[str]] = None,
        run_manager: Optional[Any] = None,** kwargs: Any,
    ) -> str:
        if stop is not None:
            raise ValueError("不支持stop参数")
        
        # 调用阿里云DashScope API
        response = dashscope.Generation.call(
            model=self.model_name,
            prompt=prompt,
            temperature=self.temperature,
            max_tokens=self.max_tokens,
            **kwargs
        )
        
        # 检查响应是否成功(修改部分)
        if hasattr(response, 'output') and hasattr(response.output, 'text'):
            return response.output.text
        else:
            raise Exception(f"LLM调用失败: {getattr(response, 'message', '未知错误')}")
    
    @property
    def _identifying_params(self) -> Mapping[str, Any]:
        return {
            "model_name": self.model_name,
            "temperature": self.temperature,
            "max_tokens": self.max_tokens
        }

# --------------------------
# 3. 准备FAQ数据
# --------------------------
faq_data = [
    {"question": "公司的工作时间是什么?", "answer": "工作日为周一至周五,上午9:00到下午6:00。"},
    {"question": "如何申请年假?", "answer": "通过HR系统提交休假申请,主管审批后生效。"},
    {"question": "有没有远程办公政策?", "answer": "支持混合办公模式,每周可在家工作最多两天。"},
    {"question": "加班有补贴吗?", "answer": "是的,超过晚上8点的加班可申请调休或加班费。"}
]

# 转换为LangChain Document格式
documents = []
for item in faq_data:
    content = f"问题: {item['question']}\n答案: {item['answer']}"
    documents.append(Document(page_content=content, metadata={"source": "faq"}))

print(f"共加载 {len(documents)} 条 FAQ 数据")

# --------------------------
# 4. 文本分割
# --------------------------
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
split_docs = text_splitter.split_documents(documents)
print(f"切分后得到 {len(split_docs)} 个文本块")

# --------------------------
# 5. 初始化嵌入模型+FAISS向量库
# --------------------------
# 1. 设置阿里云API密钥(替换为你的真实密钥),系统环境变量中设置DASHSCOPE_API_KEY即可
#DASHSCOPE_API_KEY = "your_actual_api_key_here"  # 👉 必须替换!
#dashscope.api_key = os.getenv["DASHSCOPE_API_KEY"]  # 显式初始化dashscope

# 2. 使用阿里云嵌入模型(无需下载,直接调用API)
embeddings = DashScopeEmbeddings(model_name="text-embedding-v2")

# 3. 构建FAISS向量库(无网络依赖,本地生成)
db = FAISS.from_documents(split_docs, embeddings)
db.save_local("faiss_index_dashscope")  # 保存到本地,下次可直接加载
print("FAISS向量库已保存到本地\n\n")

# --------------------------
# 6. 构建检索器+测试检索
# --------------------------
retriever = db.as_retriever(search_kwargs={"k": 2})  # 返回Top2相关结果

# 测试检索
#query = "怎么请假?"
#docs = retriever.get_relevant_documents(query)
#docs = retriever.invoke(query)
#print("\n检索结果:")
#for i, doc in enumerate(docs):
#    print(f"{i+1}. {doc.page_content}\n")

# --------------------------
# 7. 初始化LLM+创建RAG问答链
# --------------------------
llm = DashScopeLLM(
    model_name="qwen-plus",  # 可选:qwen-turbo(更快更便宜)、qwen-max(更准)
    temperature=0.3,
    max_tokens=512
)

# 定义RAG提示模板(引导模型使用检索到的上下文)
prompt_template = """严格使用以下上下文回答问题,不要编造信息。如果上下文没有相关内容,直接说"不知道"。

上下文:
{context}

问题: {question}

回答:"""
from langchain.prompts import PromptTemplate
PROMPT = PromptTemplate(template=prompt_template, input_variables=["context", "question"])

# 创建RAG链
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",  # 将检索到的上下文"填充"到提示中
    retriever=retriever,
    return_source_documents=True,
    chain_type_kwargs={"prompt": PROMPT}  # 传入自定义提示
)

# --------------------------
# 8. 问答函数+测试
# --------------------------
def ask_question(question: str):
    print(f"👉 提问: {question}")
    response=""
    try:
        result = qa_chain.invoke({"query": question})
        #print(f"✅ 回答: {result['result']}")
        response=f"✅ 回答: {result['result']}"
        # 显示参考来源
        if result["source_documents"]:
            response=response + "\n📎 参考来源:\n"
            for i, doc in enumerate(result["source_documents"]):
                response=response + f"  [{i+1}] {doc.page_content.split('答案: ')[-1]}\n"
    except Exception as e:
        # print(f"❌ 错误: {str(e)}")
        return f"❌ 错误: {str(e)}"
    return response + ("-" * 50) +"\n"

# 测试单轮问答
print( ask_question("我该怎么申请年假?") )
print( ask_question("上班时间是几点?") )
print( ask_question("可以远程办公吗?")  )
print( ask_question("远程办公有什么规定?") )
print( ask_question("那我可以一周在家三天吗?")  )


import gradio as gr
def yes_man(query, history):
    response = ask_question(query)
    return response

gr.ChatInterface(
    yes_man,
    #type="messages",
    chatbot=gr.Chatbot( height=400),
    textbox=gr.Textbox(placeholder="请在这里输入你的问题", container=False, scale=5, submit_btn="提交"),
    title="欢迎使用RAG问答系统!请问有什么可以帮助您的?",
    description="可以问我关于HR方面的问题",
    #theme="ocean",
    cache_examples=True,
).launch()

代码运行结果:

C:\Users\xiayu\miniconda3\envs\langchain03\python.exe "C:\Users\xiayu\PyCharmMiscProject\AI-Agent-Dev-Practices-Code\第10章代码\10.1-LangChain 的完整实现一个简单的 RAG 问答智能体.py"

共加载 4 条 FAQ 数据

切分后得到 4 个文本块

FAISS向量库已保存到本地

👉 提问: 我该怎么申请年假?

✅ 回答: 通过HR系统提交休假申请,主管审批后生效。

📎 参考来源:

1\] 通过HR系统提交休假申请,主管审批后生效。 \[2\] 是的,超过晚上8点的加班可申请调休或加班费。 -------------------------------------------------- 👉 提问: 上班时间是几点? ✅ 回答: 上午9:00。 📎 参考来源: \[1\] 工作日为周一至周五,上午9:00到下午6:00。 \[2\] 支持混合办公模式,每周可在家工作最多两天。 -------------------------------------------------- 👉 提问: 可以远程办公吗? ✅ 回答: 支持混合办公模式,每周可在家工作最多两天。 📎 参考来源: \[1\] 支持混合办公模式,每周可在家工作最多两天。 \[2\] 通过HR系统提交休假申请,主管审批后生效。 -------------------------------------------------- 👉 提问: 远程办公有什么规定? ✅ 回答: 远程办公需遵循混合办公模式,每周最多在家工作两天。 📎 参考来源: \[1\] 支持混合办公模式,每周可在家工作最多两天。 \[2\] 通过HR系统提交休假申请,主管审批后生效。 -------------------------------------------------- 👉 提问: 那我可以一周在家三天吗? ✅ 回答: 不可以,每周最多在家工作两天。 📎 参考来源: \[1\] 支持混合办公模式,每周可在家工作最多两天。 \[2\] 工作日为周一至周五,上午9:00到下午6:00。 -------------------------------------------------- \* Running on local URL: http://127.0.0.1:7860 \* To create a public link, set \`share=True\` in \`launch()\`. 👉 提问: 我该怎么申请年假? 访问网址http://127.0.0.1:7860,界面如下,输入"我该怎么申请年假?",查看效果: ![](https://i-blog.csdnimg.cn/direct/714d532936684e4a8c48488f64b1d01d.png) ## 一、整体演示目的(一句话版) > **构建一个基于阿里云 DashScope + LangChain + FAISS 的本地 RAG 问答系统,用于 HR FAQ 场景的原型演示。** 它的核心目标是: ✅ 不依赖 HuggingFace ✅ 使用国产大模型(通义千问) ✅ 基于自有 FAQ 知识库回答 ✅ 防止大模型胡编乱造 ✅ 提供 Web 交互界面(Gradio) *** ** * ** *** ## 二、整体运行流程(高层视角) 用户输入问题 ↓ Gradio 接收 ↓ ask_question() ↓ RetrievalQA Chain ↓ 1️⃣ FAISS 检索相关 FAQ ↓ 2️⃣ Prompt 组装(上下文 + 问题) ↓ 3️⃣ DashScope LLM 生成答案 ↓ 返回回答 + 参考来源 *** ** * ** *** ## 三、代码逐模块解析 *** ** * ** *** ### 1️⃣ 安装依赖(环境准备) pip install langchain sentence-transformers faiss-cpu pypdf fastapi uvicorn pydantic dashscope 📌 实际作用: | 包名 | 用途 | |-----------|---------------------| | langchain | RAG 编排框架 | | faiss-cpu | 本地向量数据库 | | dashscope | 阿里云 embedding / LLM | | gradio | Web UI | | pypdf | (预留)PDF 文档支持 | ⚠️ 说明: `sentence-transformers`**虽然安装了,但并未使用**,你已成功绕开它。 *** ** * ** *** ### 2️⃣ DashScopeEmbeddings(核心:向量化) class DashScopeEmbeddings(Embeddings): #### 作用 ✅ 把文本变成**向量** ✅ 用于: * FAQ 入库 * 用户提问检索 #### 调用关系 FAQ文本 → text-embedding-v2 → 向量 → FAISS #### 关键方法 | 方法 | 用途 | |-----------------|-----------| | embed_documents | FAQ 批量向量化 | | embed_query | 用户问题向量化 | ✅ 这是**整个系统"不依赖 HF"的关键点** *** ** * ** *** ### 3️⃣ DashScopeLLM(大模型接口) class DashScopeLLM(LLM): #### 作用 ✅ 封装通义千问(qwen-plus) ✅ 让 LangChain 把它当成"自己的 LLM" #### 输入 Prompt(上下文 + 问题) #### 输出 自然语言回答 *** ** * ** *** ### 4️⃣ FAQ 数据准备 faq_data = [ {"question": "...", "answer": "..."} ] #### 作用 ✅ 模拟企业内部 HR 知识库 ✅ 每条 FAQ 转成 LangChain Document Document( page_content="问题: ...\n答案: ...", metadata={"source": "faq"} ) 📌 这一步本质是: > **结构化知识 → 非结构化文本** *** ** * ** *** ### 5️⃣ 文本切分(RecursiveCharacterTextSplitter) chunk_size=500 chunk_overlap=50 #### 为什么要切? * FAQ 本身很短 * 但未来可扩展到: * PDF * 长文档 * 制度文件 ✅ 这里更多是**预留能力** *** ** * ** *** ### 6️⃣ FAISS 向量库构建 db = FAISS.from_documents(split_docs, embeddings) db.save_local("faiss_index_dashscope") #### 作用 ✅ 把所有 FAQ 向量化并存储 ✅ 支持快速相似度检索 📌 本质是一个: > **本地"语义搜索引擎"** *** ** * ** *** ### 7️⃣ 检索器(Retriever) retriever = db.as_retriever(k=2) #### 含义 ✅ 用户每问一个问题 ✅ 从 FAQ 中找 **最相关的 2 条** *** ** * ** *** ### 8️⃣ Prompt 模板(防幻觉关键) 严格使用以下上下文回答问题 如果上下文没有相关内容,直接说"不知道" #### 作用 ✅ 强制模型: * 只基于 FAQ * 不自由发挥 📌 这是 **RAG 的灵魂** *** ** * ** *** ### 9️⃣ RetrievalQA 链 qa_chain = RetrievalQA.from_chain_type( llm=llm, retriever=retriever, chain_type="stuff" ) #### 这一行做了什么? ✅ 自动完成: 1. 用户问题 2. 检索 FAQ 3. 拼 Prompt 4. 调用 LLM 5. 返回答案 📌 这就是 **LangChain 的核心价值** *** ** * ** *** ### 🔟 问答函数(业务入口) ask_question(question) #### 功能 ✅ 封装: * 调用 RAG 链 * 打印/返回回答 * 展示参考来源 ✅ 非常适合: * 单元测试 * 后端接口 * CLI 演示 *** ** * ** *** ### 1️⃣1️⃣ Gradio Web 界面 gr.ChatInterface(yes_man) #### 作用 ✅ 把命令行问答升级为: * Web 聊天界面 * 可给非技术人员演示 📌 一行代码获得: * 输入框 * 聊天窗口 * 提交按钮 *** ** * ** *** ## 四、数据流动全过程(示意图) 用户问:怎么申请年假? ↓ Gradio ↓ ask_question() ↓ RetrievalQA ↓ FAISS 检索 → "如何申请年假?" ↓ Prompt 组装 → 上下文 + 问题 ↓ Qwen LLM ↓ 回答 + 来源 ↓ Web 页面显示 *** ** * ** *** ## 五、这个 Demo 的演示目的总结 | 维度 | 目的 | |----|-------------------------| | 技术 | 展示 LangChain + 国产模型 RAG | | 业务 | HR FAQ 自动问答 | | 架构 | 本地知识库 + 云端 LLM | | 安全 | 不依赖外网 HF | | 产品 | 可给领导 / 同事演示 | | 扩展 | 可接 PDF / 数据库 / 企业微信 | *** ** * ** *** ## 六、一句话总结 > **这段代码是一个"工程级 RAG 最小可行产品(MVP)",非常适合作为企业 HR 智能问答系统的第一版原型。** ![](https://i-blog.csdnimg.cn/direct/2cb6e15633f045d791eff8fa0a992da6.png) ![](https://i-blog.csdnimg.cn/direct/e664c8c5172d46e4b43f25a7edbeae6e.png)

相关推荐
许彰午1 小时前
# 从 RAG 到 Agent:社保智能客服的进化(下)——多模态与完
人工智能
agicall.com1 小时前
信电助 - 信创话务盒 UB-A-XC 型号功能列表
人工智能·语音识别
精益数智小屋1 小时前
设备维护方案核心功能拆解:一套好的设备维护方案如何解决设备突发故障
大数据·运维·网络·数据库·人工智能·面试·自动化
.柒宇.1 小时前
AI-Agent入门实战-AI私厨
人工智能·python·langchain·agent·fastapi
默子昂1 小时前
langchain 基本使用
开发语言·python·langchain
Pushkin.1 小时前
LangGraph 多 Agent 架构与 Supervisor 模式
人工智能·架构
阿Y加油吧1 小时前
意图识别与多路由调度策略
人工智能
甲维斯1 小时前
worktree是什么鬼?Codex和Claude双修把我搞晕了!
人工智能·ai编程
极创信息1 小时前
信创软件快速适配信创改造,实战落地思路
java·大数据·数据库·人工智能·mvc·软件工程·hibernate