小林Oncall项目介绍

项目中 Agent 范式是什么

当我在看小林飞书云文档 第二张 2.2 的时候,文中说对话 agent 实现是运用了 ReAct 模式,当时对Agent的范式有点模糊了,所以去又复习了一下。

首先,Agent 的范式有两种——ReActPlan-Execute-RePlan,区别就是前者是一遍思一遍做,后者是先规划,然后执行(Execute),然后根据执行情况判断是否还需要继续执行(RePlan)

在该项目中是二者一起使用,具体的讲,详细的流程是:

  • Plan:规划器进行问题解决方案的步骤规划,分成【步骤1,步骤2,。。。。】

  • Execute: 内置 ReAct 模式

    • 思考:调用LLM,判断是否需要调用工具

    • 工具调用:MCP工具调用

    • 生成:根据工具调用返回的数据发给LLM,完成当前步骤需要的任务

  • RePlan:判断是否还有其他步骤

    • 如果没有步骤(信息完善),respond 完成响应,具体响应操作:调用LLM将各个步骤结果进行整合响应,

LangChain 和 LangGraph

作为一个java后端的学生,我对python的生态一无所知,即使在学习 Agent的时候总是听到LangChain 和 LangGraph,但是每次看到这两个的知识都是不一样的,没有一个比较明确的自我定义,导致无法对他们做定位。

LangChain:

  • 它的特点是封装:检索器,向量数据库接口,大模型调用接口,Prompt拼接等技术进行封装,降低了配置难度,类比java中的spring生态

写到这里,我有一个思考就是:为什么需要LangGraph来实现复杂的RAG流程,为什么LangChain是链式运行的RAG,在我的理解中,LangChain的作用只是帮我封装了一些传统操作,降低了配置难度,并没有考虑

这个问题目前我还理解不了他的答案,但是写几点

  • LangChain 原理是DAG有向无环图,并不是单向链式,缺点无环,无法实现节点自查再回溯到上一节点

  • LangGraph 的出现原因之一就是实现循环与条件分支,可以回溯,成为一张

    好处

    - 格式化输出:不输出零散数据,整体观感更好

    - 上下文补充:利用LLM结合问题将回答变成容易理解的语言

    - 逻辑连贯:将独立的步骤结构串联成完整分析

    • 如果剩余步骤,循环继续进行 Execute 内容

MCP_Servers

总结一下CLS mcp(集成日志服务)的功能

  • 记录每次工具调用的情况-- log_tool_call-修饰器

  • 时间字符串转时间类型外加小时偏移量-- parse_time_or_default-内部函数

  • 时间类型外加分钟偏移量--generate_time_series-内容函数

  • 获得当前时间- get_current_timestamp-mcp

  • 通过地区名字获得地区代码-get_region_code_by_name-mcp

  • 通过名字获得主题信息-get_topic_info_by_name-mcp

  • 通过服务名字获得服务信息--search_topic_by_service_name-mcp

  • 查询区间时间内某主题的日志-search_log-mcp

总结一下 monitor mcp(CPU和内存检测监控)功能

  • 记录每次工具调用的情况-- log_tool_call-修饰器

  • 时间字符串转时间类型外加小时偏移量-- parse_time_or_default-内部函数

  • 时间类型外加分钟偏移量--generate_time_series-内容函数

  • 模拟CUP增长情况并返回CPU使用情况--query_cpu_metrics

  • 模拟内存增长情况并返回内存使用情况--query_memory_metrics

感受:

开始的时候,两个mcp都是400多行的代码,感觉十分牛逼,实际上每个函数都是静态数据,实现的功能跟后端的查找没区别,从静态字典里面查找相关元素或者用模拟增长算法去模拟CPU增长,纯纯没用,只不过说的时候感觉功能十分牛逼,实际全是静态数据。

项目启动时的运行逻辑

详细的代码运行逻辑

每当项目启动后,控制台就会输出很多 logo 日志内容,我非常想知道在不进行任何api调用的情况下,单纯就是启动,代码的运行逻辑是什么

# 使用相对于 app 目录的路径:app/../aiops-docs
aiops_docs_dir = Path(__file__).parent.parent / "aiops-docs" #拼凑路径
if aiops_docs_dir.exists() and aiops_docs_dir.is_dir():#判断是否是一个目录
    md_files = list(aiops_docs_dir.glob("*.md"))#找到md的文件,然后放入列表中
    if md_files:
        try:
            result = vector_index_service.index_directory(str(aiops_docs_dir))
        except Exception as e:
            logger.error(f"❌ 知识库索引失败: {e}")

main.py 中内容

  • 拼凑出目录路径 aiops_docs_dir

  • 给目录下的所有 md 和 txt 文件加入向量数据库 vector_index_service.index_directory(str(aiops_docs_dir))

vector_index_service.py 中内容:

# 遍历并索引每个文件
for file_path in files:
    try:
        self.index_single_file(str(file_path))
        result.increment_success_count()
        logger.info(f"✓ 文件索引成功: {file_path.name}")
    except Exception as e:
        result.increment_fail_count()
        result.add_failed_file(str(file_path), str(e))
        logger.error(f"✗ 文件索引失败: {file_path.name}, 错误: {e}")

上面是 index_directory 关键代码,其他都是路径获取,判空等操作,就十分繁琐。

  • 简单讲:index_directory 作用——解析目录每一个合法文件,然后单个完成向量数据库存入

我们继续深入函数 index_single_file 关键代码:

try:
    # 1. 读取文件内容
    content = path.read_text(encoding="utf-8")
    logger.info(f"读取文件: {path}, 内容长度: {len(content)} 字符")

    # 2. 删除该文件的旧数据(如果存在)
    normalized_path = path.as_posix()#路径存储一致性
    vector_store_manager.delete_by_source(normalized_path)//1 调用一次外部函数

    # 3. 使用新的文档分割器
    documents = document_splitter_service.split_document(content, normalized_path)//2调用两次外部函数
    logger.info(f"文档分割完成: {file_path} -> {len(documents)} 个分片")

    # 4. 添加文档到向量存储
    if documents:
        vector_store_manager.add_documents(documents)//调用三次外部函数
        logger.info(f"文件索引完成: {file_path}, 共 {len(documents)} 个分片")
    else:
        logger.warning(f"文件内容为空或无法分割: {file_path}")

index_single_file 的函数逻辑:对于一个文档,

  • 删除路径一致的旧文档(vector_store_manager.delete_by_source)

  • 文档分割(document_splitter_service.split_document)

  • 把chunk加入数据库(vector_store_manager.add_documents)

vector_store_manager.delete_by_source 中的内容

核心代码

expr = f'metadata["_source"] == "{file_path}"'#删除表达式
result = collection.delete(expr)

Cllection.delete 是 pyMilvus 向量数据库的内置函数

document_splitter_service.split_document 中的内容

核心代码

# 智能分割文档 (根据文件类型选择分割器)
if file_path.endswith(".md"):
    return self.split_markdown(content, file_path)
else:
    return self.split_text(content, file_path)

当时看到这个“智能”我就想笑,只是简单的读取路径后缀做一个if判断,然后就额外封装了一个智能分割文档函数

self.split_markdown 和 self.split_text 中的内容

核心代码

# 第一阶段: 按标题分割
md_docs = self.markdown_splitter.split_text(content)

# 第二阶段: 按大小进一步分割
docs_after_split = self.text_splitter.split_documents(md_docs)

# 第三阶段: 合并太小的分片 (< 300字符)
final_docs = self._merge_small_chunks(docs_after_split, min_size=300)
#--------------------------------分割线--------------------------------------------
# 备注,init的内容补充
# Markdown 标题分割器 (只按一级和二级标题分割,减少分片数)
self.markdown_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=[
        ("#", "h1"),
        ("##", "h2"),
        # 不再按三级标题分割,避免过度碎片化
    ],
)

# 递归字符分割器 (用于二次分割,使用更大的chunk_size)
self.text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=self.chunk_size * 2,  # 加倍chunk_size,减少分片数
    chunk_overlap=self.chunk_overlap,
    length_function=len,
    is_separator_regex=False,
)

主要就三类函数

  • split_text:调用内置函数按标题分,其实就是简单的识别 h

  • split_documents:调用内置函数按照固定大小分,非语义,这里就很扯,就是暴力分割

  • merge_small_chunks:手写合并函数,合并原理就是手动加 "\n" 实现合并

vector_store_manager.add_documents 内容

核心代码

# 为每个文档生成唯一 id(因为 auto_id=False)
ids = [str(uuid.uuid4()) for _ in documents]

# LangChain Milvus 的 add_documents 会自动调用 embedding_function
# 并进行批量处理,性能更好
result_ids = self.vector_store.add_documents(documents, ids=ids)

函数内容:

  • 为documents配置id——ids

  • vector_store.add_documents Milvus 数据库内置的添加函数

最后

我们来整理一下逻辑链

  1. main.py——得到目录路径,将目录下文件加入数据库——vector_index_service.index_directory

  2. vector_index_service.index_directory 作用——解析目录每一个合法文件,然后单个(index_single_file )完成向量数据库存入

  3. vector_index_service.index_single_file

    1. 删除路径一致的旧文档(vector_store_manager.delete_by_source)

    利用内置删除函数(Cllection.delete)按路径删除chunk

    1. 文档分割(document_splitter_service.split_document)

      • 智能文档分割,其实就是判断后缀分成md分割和txt分割

      • document_splitter_service.split_markdown 和 document_splitter_service.split_text这两个函数的内置实现流程

        • split_text:调用内置函数按标题分,其实就是简单的识别 h

        • split_documents:调用内置函数按照固定大小分,非语义,这里就很扯,就是暴力分割

        • merge_small_chunks:手写合并函数,合并原理就是手动加 "\n" 实现合并

    2. 把chunk加入数据库(vector_store_manager.add_documents)

      • vector_store.add_documents Milvus 数据库内置的添加函数
  • 以上就是在项目运行之处,自主完成内置文档上传数据库
posted @ 2026-08-23 15:14  zxsoul  阅读(11)  评论(0)    收藏  举报