本 Notebook 演示如何读取 files 目录下的 .txt 文件,按空行切分段落,并构建向量检索问答助手。
前置要求:
确保已安装必要依赖 (
langchain,openai等)。确保当前 Notebook 同级目录下存在
files文件夹,且其中包含你的.txt文件。
1. 配置环境与模型¶
import os
from dotenv import load_dotenv
from openai import OpenAI
from langchain_openai import ChatOpenAI
# 加载环境变量
load_dotenv()
# 配置 API Key (如未在 .env 设置,请在此处修改)
if not os.getenv("DASHSCOPE_API_KEY"):
# os.environ["DASHSCOPE_API_KEY"] = "sk-xxxxxxxx"
print("⚠️ 请设置 DASHSCOPE_API_KEY")
os.environ["DASHSCOPE_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
# 初始化 LLM
llm = ChatOpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url=os.getenv("DASHSCOPE_BASE_URL"),
model="qwen3-coder-plus",
temperature=0,
)
# 初始化 OpenAI 客户端 (用于 Embedding)
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url=os.getenv("DASHSCOPE_BASE_URL"),
)2. 定义 Embedding 类¶
from langchain_core.embeddings import Embeddings
class DashScopeEmbeddings(Embeddings):
"""DashScope 兼容的 Embeddings 封装。"""
def __init__(self, model: str = "text-embedding-v4", dimensions: int = 1024):
self.model = model
self.dimensions = dimensions
def embed_documents(self, texts: list[str]) -> list[list[float]]:
vectors = []
# 简单批处理,每次 10 条
for i in range(0, len(texts), 10):
chunk = texts[i : i + 10]
try:
response = client.embeddings.create(
model=self.model,
input=chunk,
dimensions=self.dimensions,
)
vectors.extend([item.embedding for item in response.data])
except Exception as e:
print(f"Embedding Error: {e}")
vectors.extend([[0.0] * self.dimensions] * len(chunk))
return vectors
def embed_query(self, text: str) -> list[float]:
response = client.embeddings.create(
model=self.model,
input=[text],
dimensions=self.dimensions,
)
return response.data[0].embedding3. 读取现有文档并切分¶
此处将读取 files/ 目录下的所有 .txt 文件,并使用正则表达式按空行进行切分(也可以按嵌入模型的维度切分)。
import re
from pathlib import Path
from typing import Iterable
from langchain_core.documents import Document
def load_txt_documents(target_dir: Path) -> list[Document]:
"""读取目录下的 txt 文件并按空行分割为 Document。"""
def split_on_blank(text: str) -> Iterable[str]:
# 按dimensions维度分割(一版用于通识文档)
# for block in re.split(rf"\n\s*{{{self.dimensions}}}\s*\n", text):
# 按空行分割(兼容 Windows \r\n)
for block in re.split(r"\n\s*\n", text):
cleaned = block.strip()
if cleaned:
yield cleaned
documents: list[Document] = []
# 检查目录
if not target_dir.exists():
print(f"❌ 错误: 目录 {target_dir} 不存在!请确认当前路径。")
return []
files = sorted(target_dir.glob("*.txt"))
if not files:
print(f"❌ 警告: {target_dir} 下没有找到 .txt 文件")
return []
for path in files:
print(f"📄 正在读取: {path.name}")
content = path.read_text(encoding="utf-8")
for idx, part in enumerate(split_on_blank(content)):
documents.append(
Document(
page_content=part,
metadata={"source": path.name, "chunk_id": idx},
)
)
return documents4. 构建向量库¶
from langchain_core.vectorstores import InMemoryVectorStore
def build_vector_store() -> InMemoryVectorStore:
# 指定 files 目录 (相对于当前 Notebook)
target_dir = Path.cwd() / "files"
documents = load_txt_documents(target_dir)
embeddings = DashScopeEmbeddings()
vector_store = InMemoryVectorStore(embedding=embeddings)
if documents:
vector_store.add_documents(documents)
print(f"\n✅ 嵌入完成: 共 {len(documents)} 个片段。")
return vector_store
# 立即构建
vector_store = build_vector_store()📄 正在读取: question.txt
✅ 嵌入完成: 共 8 个片段。
5. 创建检索 Agent¶
from langchain.agents import create_agent
from langchain.tools import tool
def create_react_agent(vector_store: InMemoryVectorStore):
"""创建带检索工具的 ReAct Agent。"""
@tool(response_format="content_and_artifact")
def retrieve_context(query: str):
"""基于向量库检索与问题最相关的文本片段。"""
# 打印日志方便调试
print(f" 🔎 [检索中] Query: {query}")
retrieved = vector_store.similarity_search(query, k=3)
serialized = "\n\n".join(
f"[{doc.metadata['source']}#{doc.metadata['chunk_id']}] {doc.page_content}"
for doc in retrieved
)
return serialized, retrieved
return create_agent(
llm,
tools=[retrieve_context],
system_prompt=(
"你可以使用检索工具获得参考资料。回答时结合检索到的内容,"
"如有必要可以在答案中简单引用来源标识。"
),
)6. 提问测试¶
请根据你的 files 目录中的文档内容修改下面的 query 变量。
# 初始化 Agent
agent = create_react_agent(vector_store)
# === 修改此处的问题 ===
query = "考勤缺卡怎么处理?"
print(f"🚀 用户问题: {query}\n")
# 流式输出回答
for event in agent.stream({"messages": [{"role": "user", "content": query}]}, stream_mode="values"):
event["messages"][-1].pretty_print()🚀 用户问题: 考勤缺卡怎么处理?
================================ Human Message =================================
考勤缺卡怎么处理?
================================== Ai Message ==================================
Tool Calls:
retrieve_context (call_f714d4e689774d979ecafd22)
Call ID: call_f714d4e689774d979ecafd22
Args:
query: 考勤缺卡怎么处理?
🔎 [检索中] Query: 考勤缺卡怎么处理?
================================= Tool Message =================================
Name: retrieve_context
[question.txt#3] 问题:忘记打卡怎么办?
答案:员工缺卡可在缺卡发生后的3个工作日内申请补卡,每人每月限3次,超限不再受理。未补卡者,每缺1次按缺勤半天计算,扣发半天工资。
权限:
关键词:因公外出、打卡处理
[question.txt#4] 问题:因公外出无法回公司打卡怎么办?
答案:允许打外勤卡,需走外出申请并注明原因;外出申请路径:飞书/更多/工作台/人力资源&行政/假勤/申请/外出。
权限:
关键词:因公外出,无法打卡
[question.txt#2] 问题:考勤有问题找谁?
答案:找LarryLi。
权限:
关键词:考勤问题
================================== Ai Message ==================================
如果你遇到了考勤缺卡的问题,可以根据以下情况处理:
1. **忘记打卡**:根据规定,员工缺卡可在缺卡发生后的3个工作日内申请补卡,每人每月限补卡3次,超过限制将不再受理。如果未能及时补卡,每缺卡一次将按缺勤半天计算,并扣发半天工资。[[3]]
2. **因公外出无法回公司打卡**:如果是因公外出导致无法打卡,可以申请打外勤卡。你需要提前走外出申请流程并注明原因。具体操作路径为:飞书 → 更多 → 工作台 → 人力资源&行政 → 假勤 → 申请 → 外出。[[4]]
如果有其他考勤相关问题,可以联系 LarryLi 进行咨询。[[2]]