说明
Solon 的流程编排,使用了 solon-flow 做流程编排,因此需要先对 solon-flow 有所了解,下面是 Solon flow的一些简单介绍,更具体的介绍可以参考官网 https://solon.noear.org/article/learn-solon-flow 。
solon-flow
Solon Flow 提供基础的流引擎能力,支持开放式的驱动定制(像 JDBC 有 MySQL 或 PostgreSQL 等不同驱动一样)。可用于业务规则、决策处理、计算编排、流程审批等场景。
核心概念:
- 链、节点、连接。
- 链上下文、链驱动器、任务组件接口、条件组件接口。
- 流引擎。
概念关系:
- 链(Chain),一个完整的流程,由多个节点(Node)连接(Link)组成。一个链有且只有一个 start 类型的节点。从 start 节点开始,顺着连接(Link)流出。
- 节点(Node),流程处理的环节,会有多个连接(Link),有流入连接,流出连接。
- 连接(Link),节点与节点之间的执行顺序。连接向其它节点,称为流出连接。被其它节点连接,称为流入连接。
- 流引擎,提供执行时的环境(链上下文与对象引用等),驱动链的流动(执行)。链的流转过程,可以有上下文参数(ChainContext),可以被中断(可支持有状态的审批模式)。
通俗些,就是通过点(节点) + 线(连接)来描述一个流程图(链)。因此这里列出节点和连接的属性。
节点 Node 属性
| 属性 |
数据类型 |
需求 |
描述 |
| id |
String |
|
节点Id(要求链内唯一)。不配置时,会自动生成 |
| type |
NodeType |
|
节点类型,类型包括 start,execute,inclusive,exclusive,parallerl,end。不配置时,缺省为 execute 类型, |
| title |
String |
|
显示标题 |
| meta |
Map |
|
元信息(用于应用扩展) |
| link |
String or Link String[] or Link[] |
|
连接(支持单值、多值),不配置时,会自动生成。link 全写配置风格为 Link 类型结构;简写配置风格为 Link 的 nextId 值(即 String 类型) |
| task |
String |
|
任务描述(会触发驱动的 handleTask 处理) |
| when |
String |
|
执行任务条件描述(会触发驱动的 handleTest 处理) |
连接 Link 属性
通常可以不配置,或者用简写模式配置即可,只要当Node是包含网关(inclusive)或者排他网关(exclusive)时需要配置全选风格,从而配置条件。
| 属性 |
数据类型 |
需求 |
描述 |
| nextId |
String |
必填 |
后面的节点Id |
| title |
String |
|
显示标题 |
| meta |
Map |
|
元信息(用于应用扩展) |
| condition |
String |
|
流出条件描述(会触发驱动的 handleTest 处理) |
节点类型 NodeType
|
描述 |
任务 |
连接条件 |
可流入 连接数 |
可流出 连接数 |
图例参考 |
| start |
开始 |
/ |
/ |
0 |
1 |
|
| execute |
执行节点(缺省类型) |
可有 |
/ |
1…n |
1 |
|
| inclusive |
包容网关 |
/ |
支持 |
1…n |
1…n |
|
| exclusive |
排它网关 |
/ |
支持 |
1…n |
1…n |
|
| parallel |
并行网关 |
/ |
/ |
1…n |
1…n |
|
| end |
结束 |
/ |
/ |
1…n |
0 |
|
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| dependencies { implementation platform(project(":demo-parent"))
implementation("org.noear:solon-web") implementation("org.noear:solon-view-enjoy") implementation("org.noear:solon-ai") implementation("org.noear:solon-logging-logback") implementation("org.noear:solon-openapi2-knife4j") implementation("org.noear:solon-web-rx") implementation("org.noear:solon-web-sse") implementation("org.noear:solon-flow") implementation("org.dromara.hutool:hutool-all")
testImplementation("org.noear:solon-test") }
|
配置
app.yml
这里需要指定配置的流配置文件的位置。
1 2
| solon.flow: - "classpath:flow/*"
|
流配置
这里尝试用一个模拟一次简单的诊断,从诊断到治疗建议等流程。注意这里只是示例,并非真正的诊断流程,更完整的流程是还需要更多的病人信息(比如病史,检查,检验等),检索相关知识库等。这里只是为了 solon-flow 针对 ai 相关的编排的逻辑。
在诊断的节点,我们使用了deepseek-r1,在治疗环境,我们使用qwen2.5。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| id: "ai-flow-01" layout: - id: "开始" type: "start" - id: "诊断" type: "execute" meta.model: "deepseek-r1:32b" meta.apiUrl: "http://127.0.0.1:11434/api/chat" meta.provider: "ollama" meta.input: "prompt" meta.output: "intention" meta.system: "根据用户的描述,判断用户最可能的三个健康问题,只要诊断名称,不需要其他解释,用 Markdown 的列表格式返回。" task: "@intentionTask" - id: "治疗建议" type: "execute" meta.model: "qwen2.5:7b" meta.apiUrl: "http://127.0.0.1:11434/api/chat" meta.provider: "ollama" meta.input: "intention" meta.output: "suggestion" meta.system: "#角色\n你是一个经验丰富的医生\n\n#任务\n根据用户提供的诊断信息,提供治疗建议" task: "@suggestionTask" - type: "end"
|
Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.example.demo.ai.llm.controller;
import com.example.demo.ai.llm.service.LlmService; import com.jfinal.kit.Kv; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.noear.solon.annotation.*;
@Controller @Mapping("/llm") @Api("聊天") public class LlmController { @Inject private LlmService service;
@ApiOperation("flow") @Post @Mapping("flow") public Kv flow(String prompt) { return service.flow(prompt); }
@ApiOperation("aiFlow") @Post @Mapping("aiFlow") public Kv aiFlow(String prompt) { return service.aiFlow(prompt); } }
|
这里的 flow 是演示基础的 solon flow 的调用,是我学习solon flow 时编写的一个简单例子,不熟悉的可以先看看这个示例的编写。
aiFlow 是对大模型的一个编排的调用。
Service
这里的重点就是注入 FlowEngine
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| package com.example.demo.ai.llm.service;
import com.jfinal.kit.Kv; import org.noear.solon.annotation.Component; import org.noear.solon.annotation.Inject; import org.noear.solon.flow.ChainContext; import org.noear.solon.flow.FlowEngine;
@Component public class LlmService { @Inject private FlowEngine flowEngine;
public Kv flow(String prompt) {
try { ChainContext chainContext = new ChainContext(); chainContext.put("prompt", prompt); Kv kv = Kv.create(); chainContext.put("result", kv); flowEngine.eval("c1", chainContext);
return kv; } catch (Throwable e) { throw new RuntimeException(e); } }
public Kv aiFlow(String prompt) { try { ChainContext chainContext = new ChainContext(); chainContext.put("prompt", prompt); Kv kv = Kv.create(); chainContext.put("result", kv); flowEngine.eval("ai-flow-01", chainContext);
return kv; } catch (Throwable e) { throw new RuntimeException(e); } } }
|
IntentionTask
这里是诊断节点的处理,根据 meta 信息和用户的输入构建大模型,然后进行调用,并将结果通过 ChainContext 进行传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| package com.example.demo.ai.llm.service;
import com.jfinal.kit.Kv; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern;
import org.dromara.hutool.core.regex.ReUtil; import org.dromara.hutool.core.text.StrUtil; import org.noear.solon.ai.chat.ChatConfig; import org.noear.solon.ai.chat.ChatModel; import org.noear.solon.ai.chat.ChatResponse; import org.noear.solon.ai.chat.message.ChatMessage; import org.noear.solon.annotation.Component; import org.noear.solon.flow.ChainContext; import org.noear.solon.flow.Node; import org.noear.solon.flow.TaskComponent;
@Component("intentionTask") public class IntentionTask implements TaskComponent { @Override public void run(ChainContext context, Node node) throws Throwable { Kv meta = Kv.create().set(node.meta()); ChatConfig chatConfig = new ChatConfig(); chatConfig.setModel(meta.getStr("model")); chatConfig.setProvider(meta.getStr("provider")); chatConfig.setApiUrl(meta.getStr("apiUrl")); chatConfig.setTimeout(Duration.ofSeconds(600)); ChatModel chatModel = ChatModel.of(chatConfig).build();
List<ChatMessage> chatMessageList = new ArrayList<>(); ChatMessage system = ChatMessage.ofSystem(meta.getStr("system")); chatMessageList.add(system);
Kv model = Kv.create().set(context.model()); String inputKey = meta.getStr("input"); ChatMessage userMessage = ChatMessage.ofUser(model.getStr(inputKey)); chatMessageList.add(userMessage); ChatResponse response = chatModel.prompt(chatMessageList).call(); String content = response.getMessage().getContent();
String pattern = "<think>.*?</think>"; Pattern p = Pattern.compile(pattern, Pattern.DOTALL); content = StrUtil.trim(ReUtil.replaceAll(content, p, ""));
String outputKey = meta.getStr("output"); context.put(outputKey, content);
Kv result = context.get("result"); result.set("data", content); } }
|
SuggestionTask
这里是治疗节点的处理,根据 meta 信息和上一级节点的输入构建大模型,然后进行调用,并将结果通过 ChainContext 进行返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package com.example.demo.ai.llm.service;
import com.jfinal.kit.Kv; import java.util.ArrayList; import java.util.List; import org.noear.solon.ai.chat.ChatConfig; import org.noear.solon.ai.chat.ChatModel; import org.noear.solon.ai.chat.ChatResponse; import org.noear.solon.ai.chat.message.ChatMessage; import org.noear.solon.annotation.Component; import org.noear.solon.flow.ChainContext; import org.noear.solon.flow.Node; import org.noear.solon.flow.TaskComponent;
@Component("suggestionTask") public class SuggestionTask implements TaskComponent { @Override public void run(ChainContext context, Node node) throws Throwable { Kv meta = Kv.create().set(node.meta()); ChatConfig chatConfig = new ChatConfig(); chatConfig.setModel(meta.getStr("model")); chatConfig.setProvider(meta.getStr("provider")); chatConfig.setApiUrl(meta.getStr("apiUrl")); ChatModel chatModel = ChatModel.of(chatConfig).build();
List<ChatMessage> chatMessageList = new ArrayList<>(); ChatMessage system = ChatMessage.ofSystem(meta.getStr("system")); chatMessageList.add(system);
Kv model = Kv.create().set(context.model()); String inputKey = meta.getStr("input"); ChatMessage userMessage = ChatMessage.ofUser(model.getStr(inputKey)); chatMessageList.add(userMessage); ChatResponse response = chatModel.prompt(chatMessageList).call(); String content = response.getMessage().getContent();
String outputKey = meta.getStr("output"); context.put(outputKey, content);
Kv result = context.get("result"); result.set("data", content); } }
|
验证
通过 swagger 直接调用aiFlow。

这里是中间环节的输出。

小结
Solon AI 通过 Solon Flow 进行编排。通过编排,我们可以整合普通的业务逻辑和大模型的生成式功能实现更复杂、更人性化的业务逻辑,当然要实现真实可用的业务逻辑,需要自己进一步的摸索。