环曜Agent核心功能拆解:RAG、工作流与AI数据分析的实现原理

前两篇分别讲了架构和部署,这篇来深入聊聊环曜Agent最吸引人的三个功能:RAG知识库、可视化工作流和AI数据分析。我会结合源码,尽量把实现原理讲清楚,而不是停留在功能介绍层面。

一、RAG知识库:从文档到智能回答

RAG(Retrieval-Augmented Generation)是环曜Agent知识库的核心。它的价值在于让大模型能基于私有文档回答问题,而不是瞎编。环曜Agent的RAG实现有几个值得说道的细节。

1.1 文档处理流水线

用户上传一个PDF,后台经历了什么?我梳理了一下流程:

PDF/DOCX上传
  ↓
LibreOffice转PDF(如果是Office文档)
  ↓
PDFBox提取文本 + 页码信息
  ↓
智能分块(按段落/语义切分)
  ↓
Embedding向量化
  ↓
存入Elasticsearch(向量+全文索引)

智能分块这个环节比较关键。环曜Agent没有简单按固定字数切分,而是做了语义完整性判断。源码里能看到它尽量在段落边界处切分,避免把一句话拦腰截断。这样做的好处是检索时每个chunk都是语义完整的,注入Prompt后模型更容易理解上下文。

1.2 混合检索策略

环曜Agent的检索不是单纯的向量相似度,而是向量检索 + BM25全文检索的混合方案:

# application.properties里的配置
rag.enable-hybrid-search=true
rag.bm25-weight=0.3
rag.vector-weight=0.7

混合检索的意义在于互补:

  • 向量检索:擅长语义匹配,比如用户问"怎么退款",能匹配到"退货流程"相关的内容
  • BM25检索:擅长关键词精确匹配,比如用户问某个特定的产品型号,能精确命中

环曜Agent的做法是分别用两种方式检索,然后按权重合并结果。向量占70%、BM25占30%这个配比,在通用场景下比较均衡。如果你的场景关键词精确匹配更重要,可以调大BM25的权重。

1.3 重排序优化

第一次检索可能返回几十个结果,但不是每个都相关。环曜Agent做了重排序(Rerank)

用户提问 → 混合检索Top 15 → 重排序 → 取Top 5 → 注入Prompt

重排序的作用是把最相关的内容排到前面。因为混合检索的打分方式不同,直接合并的结果顺序不一定最优。重排序模型会对候选结果重新打分,通常能显著提升最终答案的质量。

1.4 原文定位功能

这个功能挺实用的。当AI基于知识库回答时,可以显示答案引用自哪篇文档的哪一页。实现原理是:

  1. 文档处理时,PDFBox提取文本的同时记录每段文字的页码和坐标
  2. 分块时把位置信息一起存入ES
  3. 检索返回结果时,把位置信息一并带回
  4. 前端展示时,生成原文链接,点击可以跳转到对应位置

这个功能依赖LibreOffice把文档转成PDF,所以README里反复强调推荐安装LibreOffice。如果没装,功能会降级,只能显示文档名,不能精确定位。

1.5 本地Embedding降级方案

环曜Agent有个很贴心的设计:LocalEmbeddingModel。当用户没有配置API Key时,系统会自动用这个本地模型做Embedding。

它的实现基于n-gram哈希特征,512维向量。虽然效果不如OpenAI的text-embedding-ada-002,但胜在:

  • 完全本地运行,不依赖外网
  • 零成本,不消耗API额度
  • 启动时自动初始化,无需额外配置

源码在LocalEmbeddingModel.java里,有兴趣可以看看。对于内网环境或者预算有限的场景,这个降级方案很实用。

二、可视化工作流:拖拽背后的执行引擎

工作流编排是环曜Agent的另一个亮点。前端看着是拖拽连线,后端其实是个完整的执行引擎。

2.1 节点类型设计

环曜Agent支持7种节点类型:

节点类型作用典型场景
LLM节点调用大模型生成内容文本生成、意图识别
条件节点根据条件分支执行if/else逻辑判断
循环节点批量数据处理遍历列表、批量分析
工具节点调用外部工具/API查天气、调数据库
变量节点数据传递和转换格式转换、字段提取
定时节点定时触发执行定时报表、定时巡检
结束节点流程结束输出返回最终结果

这些节点基本覆盖了常见的自动化场景。更复杂的需求可以通过组合实现,比如:

定时触发 → 读取数据库 → 循环处理每条记录 → LLM分析 → 条件判断 → 发送通知

2.2 执行引擎原理

工作流的执行不是简单的顺序执行,而是基于拓扑排序的有向无环图(DAG)执行:

// 伪代码,核心逻辑在 WorkflowEngine.java
public class WorkflowEngine {
    public ExecutionResult execute(Workflow workflow, Map<String, Object> inputs) {
        // 1. 构建DAG
        Graph graph = buildGraph(workflow.getNodes(), workflow.getEdges());
        
        // 2. 拓扑排序,确定执行顺序
        List<Node> executionOrder = topologicalSort(graph);
        
        // 3. 按顺序执行每个节点
        WorkflowContext context = new WorkflowContext(inputs);
        for (Node node : executionOrder) {
            NodeExecutor executor = getExecutor(node.getType());
            NodeResult result = executor.execute(node, context);
            context.setOutput(node.getId(), result);
        }
        
        // 4. 返回最终结果
        return buildResult(context);
    }
}

拓扑排序保证了依赖关系:如果节点B依赖节点A的输出,那A一定在B之前执行。如果用户连成了环(A依赖B,B又依赖A),系统会检测并报错。

2.3 变量传递与表达式

节点之间怎么传数据?环曜Agent设计了一套变量引用机制

节点A输出: {"summary": "这是一段摘要"}
节点B输入: {{nodeA.output.summary}}

双大括号语法表示引用其他节点的输出。执行时,引擎会解析表达式,从上下文里取值替换。这套机制虽然简单,但足够支撑大多数数据传递场景。

2.4 错误处理与重试

工作流执行难免出错,环曜Agent做了几层保护:

  1. 节点级重试:每个节点可以配置重试次数和间隔
  2. 错误分支:条件节点可以判断"上一个节点是否失败"
  3. 超时控制:防止某个节点卡住导致整个流程挂起
  4. 日志记录:每个节点的输入输出都记录,方便排查

这些设计让工作流在 production 环境下更可靠。毕竟自动化流程跑在后台,出了问题不能指望用户手动重试。

三、AI数据分析:自然语言查询数据库

这是环曜Agent最有特色的功能,也是我花时间研究最多的部分。

3.1 整体流程

用户输入自然语言
  ↓
意图识别(判断是不是查询意图)
  ↓
获取数据源表结构/索引信息
  ↓
构建Prompt(包含表结构 + 用户问题)
  ↓
调用LLM生成SQL/DSL
  ↓
安全验证(四层防护)
  ↓
执行查询(只读连接)
  ↓
返回结果 + 生成的SQL + AI分析

3.2 Prompt工程

Prompt的设计直接决定了生成SQL的质量。环曜Agent的Prompt大致长这样:

你是一个数据分析助手。根据下面的表结构,将用户的问题转换为SQL查询。

表结构:
表名: orders
字段: 
  - id (INT, 主键)
  - user_id (INT, 用户ID)
  - amount (DECIMAL, 订单金额)
  - status (VARCHAR, 订单状态)
  - created_at (DATETIME, 创建时间)

约束:
- 只能生成SELECT查询
- 禁止INSERT/UPDATE/DELETE/DROP/TRUNCATE
- 使用标准SQL语法

用户问题:最近7天的订单总额是多少?

请生成SQL:

Prompt里包含几个关键要素:

  • 表结构信息:字段名、类型、注释,帮助模型理解数据含义
  • 约束条件:明确告诉模型只能生成查询语句
  • 示例引导:如果有历史查询记录,会作为示例加入Prompt

3.3 四层安全防护

AI生成SQL最大的风险是安全问题。环曜Agent做了四层防护,我逐层分析:

第一层:意图识别

在调用LLM之前,先判断用户的问题是不是查询意图。如果是"删除所有数据"这种明显危险的意图,直接拦截。实现方式可以用简单的关键词匹配,也可以用一个小模型做分类。

第二层:Prompt约束

在Prompt里明确写入安全规则:

  • “只能生成SELECT查询”
  • “禁止INSERT/UPDATE/DELETE/DROP/TRUNCATE”
  • “禁止修改数据库结构”

LLM对Prompt里的约束通常比较听话,尤其是GPT-4和通义千问这种强模型。

第三层:SQL验证

LLM生成的SQL不是直接执行,而是先过一遍正则检查:

// 伪代码
public boolean isSafe(String sql) {
    String upper = sql.toUpperCase();
    // 禁止的SQL关键字
    String[] forbidden = {"INSERT", "UPDATE", "DELETE", "DROP", "TRUNCATE", "ALTER"};
    for (String keyword : forbidden) {
        if (upper.contains(keyword)) {
            return false;
        }
    }
    // 必须以SELECT开头
    return upper.trim().startsWith("SELECT");
}

这层是硬规则,不管LLM生成了什么,只要包含危险关键字就直接拒绝。

第四层:数据库只读连接

最后一道防线:执行SQL时用的数据库连接是只读权限的。即使前面三层都被绕过了,只读连接也做不了破坏。

# 配置只读数据源
spring.datasource.read-only=true

这四层防护从应用层到数据库层层层递进,在安全和可用性之间做了不错的平衡。

3.4 同义词与口语化理解

业务人员不会用精确的字段名提问。比如表里有created_at字段,用户可能问"下单时间"、“什么时候买的”、“购买日期”。

环曜Agent的解决方案是领域词典

  1. 管理员可以配置同义词映射(“下单时间” -> “created_at”)
  2. 用户提问时,先做同义词替换
  3. 替换后的文本再送入LLM生成SQL

这个功能在领域词典管理页面配置,对于业务术语多的场景(比如医疗、金融)特别有用。

3.5 Elasticsearch DSL生成

除了MySQL,环曜Agent还支持自然语言查询Elasticsearch。生成DSL的逻辑和SQL类似,但Prompt里要教LLM DSL语法:

ES索引结构:
索引: logs
字段:
  - timestamp (date)
  - level (keyword)
  - message (text)

请生成Elasticsearch DSL查询...

ES DSL比SQL复杂,生成准确率会稍低一些。但对于日志分析、全文搜索场景,这个功能能省不少事。

四、Function Calling:扩展AI的"手脚"

Function Calling让AI能调用外部工具,是智能体从"能说"到"能做"的关键。

4.1 内置工具集

环曜Agent内置了7个工具:

工具功能使用场景
天气查询查指定城市天气出行助手、生活咨询
计算器数学运算财务计算、数据分析
数据库查询执行SQL数据查询、报表生成
Web搜索联网搜索实时信息获取
URL阅读读取网页内容资讯摘要、竞品分析
当前时间获取时间时效性回答
工作流调用触发工作流复杂任务自动化

4.2 工具注册与发现

工具的注册机制设计得挺灵活:

@Component
public class ToolRegistry {
    private Map<String, Tool> tools = new HashMap<>();
    
    public void register(Tool tool) {
        tools.put(tool.getName(), tool);
    }
    
    public Tool getTool(String name) {
        return tools.get(name);
    }
    
    public List<Tool> getAllTools() {
        return new ArrayList<>(tools.values());
    }
}

每个工具实现统一的Tool接口, Spring启动时自动扫描注册。新增工具只需要写个类加@Component注解,不需要改其他代码。

4.3 工具调用流程

用户提问 → LLM判断是否需要工具 → 生成工具调用参数 → 执行工具 → 结果返回LLM → 生成最终回答

LangChain4j封装了大部分细节,开发者只需要实现工具的执行逻辑。比如天气查询工具:

@Tool("查询指定城市的天气")
public String getWeather(@P("城市名,如北京、上海") String city) {
    // 调用天气API
    return weatherApi.query(city);
}

@Tool@P注解会自动生成工具描述,LLM根据描述判断什么时候调用这个工具。

五、小结

环曜Agent这三个核心功能的实现,有几个共同的设计思路:

  1. 分层架构:每个功能都有清晰的层次,RAG分文档处理/检索/生成,工作流分编辑/执行,数据分析分意图识别/SQL生成/执行
  2. 安全优先:尤其是AI数据分析,四层防护从应用到数据库层层把关
  3. 降级策略:LocalEmbeddingModel、只读连接等设计,保证系统在资源受限时也能运行
  4. 扩展性:工具注册、领域词典、模型配置等,都留了扩展接口

下篇文章我会从源码层面解读环曜Agent的代码结构,并分享二次开发的经验。如果你想在这个基础上定制自己的功能,下篇应该对你有帮助。


本文基于环曜Agent实际项目经验撰写,仅供技术交流参考。

关于环曜Agent

环曜Agent是一家专注于企业级AI智能体本地化部署的服务商,提供从平台搭建、模型适配到二次开发的全栈技术支持。我们致力于帮助企业构建安全可控、私有化部署的AI智能体平台,让非技术人员也能轻松创建和管理AI助手。

服务范围:智能体平台部署 | RAG知识库搭建 | 工作流自动化 | AI数据分析 | 模型本地化适配 | 企业定制开发

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐