# flowEngine
**Repository Path**: mq/flow-engine
## Basic Information
- **Project Name**: flowEngine
- **Description**: No description available
- **Primary Language**: Python
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2025-10-21
- **Last Updated**: 2025-11-03
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Flow Engine Demo
一个基于 JSON 的简易流程引擎,支持条件分支、HTTP 请求与任务分配节点,并提供 FastAPI 管理与编排接口。
## 项目结构(FastAPI 最佳实践)
- `api/` 后端服务模块
- `app.py`:FastAPI 入口,注册中间件、异常处理与路由,并在 lifespan 启动阶段初始化数据库与导出 OpenAPI
- `routers/flows_controller.py`:流程管理与运行相关路由(创建/更新/删除/运行/校验/Mermaid 导出)
- `routers/runs_controller.py`:运行记录相关路由(列表/详情/停止)
- `routers/dto`:Pydantic DTO(Flow/Run 的请求体与参数,按路由模块拆分)
- `utils.py`:通用工具(统一返回封装、异常处理注册、Mermaid 生成、默认引擎判断、OpenAPI 导出、运行器注册)
- `db.py`:数据库模型与 CRUD(SQLite + SQLAlchemy)
- `flow_engine/` 引擎包(IO/Core/Nodes/Conditions/Execution)
- `run_flow.py`:命令行示例入口(可选)
- `openapi.json`:服务启动时自动导出的 OpenAPI 文档
## 快速开始
- 环境要求:Python 3.8+(建议 3.10/3.12)
- 使用 uv 管理依赖(推荐):
- 安装项目依赖(基于 `pyproject.toml` 与锁文件):
```bash
uv sync
```
- 开发模式安装(可编辑安装):
```bash
uv pip install -e .
```
- 新增依赖(示例:添加 Kafka 客户端):
```bash
uv add aiokafka>=0.10.0
```
- 移除依赖(示例):
```bash
uv remove aiokafka
```
- 或使用 pip(可选):
- 开发模式安装项目依赖:
```bash
python -m pip install -e .
```
- 或仅安装核心依赖:
```bash
python -m pip install fastapi uvicorn sqlalchemy langgraph aiokafka
```
- 启动服务(项目根目录,推荐使用 uv):
```bash
uv run uvicorn api.app:app --host 127.0.0.1 --port 8000 --reload
```
- 或直接脚本运行(亦可用 uv 执行):
```bash
uv run python api/app.py
```
- 访问接口:
- Swagger UI:http://127.0.0.1:8000/docs
## 接口概览
- Flows(前缀 /flows):
- POST `/flows` 创建流程(校验后入库)
- POST `/flows/list` 列出流程
- POST `/flows/get` 获取流程详情
- POST `/flows/update` 更新流程
- POST `/flows/delete` 删除流程
- POST `/flows/run` 运行流程(支持 engine: flow/lang/demo)
- POST `/flows/run_demo` 使用 DemoRunner 运行流程
- POST `/flows/validate` 检查流程是否可运行(是否有 Start 节点等)
- POST `/flows/mermaid` 将流程转为 Mermaid 代码(`flowchart TD`)
- Runs(前缀 /runs):
- POST `/runs/list` 列出运行记录
- POST `/runs/get` 获取运行详情(含 trace/context)
- POST `/runs/stop` 停止正在运行的流程
## 运行引擎与配置
- 引擎选择:
- `flow`:串行执行(FlowRunner)
- `lang`:基于 LangGraph 的状态图执行(LangGraphRunner)
- `demo`:演示用(DemoRunner)
- 默认引擎:可通过环境变量 `FLOW_DEFAULT_ENGINE` 设置(支持 `flow`/`lang`/`demo`,当 `langgraph` 未安装时自动回退为 `flow`)。
- CORS:默认允许所有来源、方法与请求头。
- OpenAPI 导出:服务启动后会生成 `openapi.json` 于项目根目录。
## 数据库说明
- 使用 SQLite,数据库文件:`flow_engine_demo.db`
- 首次启动会根据根目录下 `schema.sql` 初始化数据库结构;若缺失则回退为 ORM 自动创建
## 引擎包概览
- 架构:入口 -> FlowGraph -> FlowRunner/LangGraphRunner/DemoRunner -> 节点执行 -> 返回 context 与 trace
- 主要模块:
- IO:`flow_engine/io/loader.py`
- Core:`flow_engine/core/graph.py`, `edge.py`
- Nodes:`flow_engine/nodes/*`
- Conditions:`flow_engine/conditions/condition.py`
- Execution:`flow_engine/execution/runner.py`
更多设计细节请查看 `DESIGN.md` 与测试说明 `TESTING.md`。
## 分层架构(Controller / Service / Manager / DAO)
为便于后续扩展与维护,后端服务遵循更细粒度的分层模式,参考 Java 工程中的 MVC 乃至 Controller/Service/Manager/DAO:
- Controller(FastAPI Routers):负责 HTTP 路由与入参校验,仅做轻薄的控制与异常转换
- 位置:`api/routers/flows_controller.py`, `api/routers/runs_controller.py`
- 行为:解析请求体,调用 Service 层,统一封装返回(`utils.envelope`),将业务错误映射为 HTTP 状态码
- Service(业务编排):负责具体的业务流程与跨模块编排
- 位置:`api/services/flows_service.py`, `api/services/runs_service.py`
- 行为:组合 DAO 与 Manager 完成“创建/更新/删除流程”“运行流程”“查询/停止运行”等业务;做必要的校验(如构建 FlowGraph 验证)
- Manager(资源/运行器管理):负责与执行器、外部资源交互的中间管理层
- 位置:`api/managers/engine_manager.py`
- 行为:根据 `engine` 选择并创建 Runner(Flow/LangGraph/Demo),维护内存中的运行器注册表,提供停止运行的接口
- DAO(数据访问):封装数据库 CRUD,保证数据访问的一致性与可替换性
- 位置:`api/dao/flows_dao.py`, `api/dao/runs_dao.py`
- 行为:调用 `api/db.py` 中的具体实现,将其统一暴露为模块化方法
分层调用路径示例:
Controller -> Service -> (Manager + DAO)
好处:
- 解耦路由与业务逻辑,便于单元测试与后续扩展
- 业务编排集中于 Service,复用 Manager 的运行器管理与 DAO 的数据访问
- 与常见企业级工程实践一致(更清晰的职责划分、可插拔的实现层)
## Kafka 集成与使用
- 依赖:已在 的 dependencies 中加入 `aiokafka>=0.10.0`
- 配置:通过环境变量 `KAFKA_BOOTSTRAP_SERVERS` 指定 Kafka 集群地址,默认 `localhost:9092`
- 模块位置:
- Manager 层:(提供 KafkaManager 与全局单例 KAFKA_MANAGER)
- Service 层:(提供便捷服务方法)
- 生产消息(推荐通过 Service 层):
- 发送 JSON:`kafka_send_json(topic, obj, key=None, headers=None)`
- 发送字节:`kafka_send_bytes(topic, value: bytes, key=None, headers=None)`
- 消费消息:
- 创建消费任务:`kafka_consumer_task(topics: List[str], group_id: str, handler: Callable[[Any], Awaitable[None]], decode_json=True)`
- 将返回的异步函数交给 FastAPI 的 BackgroundTasks 执行(例如在路由中 `bg.add_task(task)`)
- 处理函数 `handler` 为异步函数;当 `decode_json=True` 时,消费者会尝试把字节消息按 UTF-8 解码并 `json.loads`
- 停止消费者:`kafka_stop_consumer(group_id, topics)`
- 生命周期管理:
- 应用关闭阶段(FastAPI lifespan)会自动调用 `KAFKA_MANAGER.close()`,优雅关闭 Producer/Consumer,避免资源泄露
- 端点集成建议:
- 如需对外暴露发送消息或启动/停止消费者的 REST 接口,可在 `api/routers` 中新增路由,并直接调用上述 Service 方法
### Binlog 消费与处理(Canal JSON)
- 启动时自动开启消费者:在 的 lifespan 启动阶段中,基于绑定 "binlog-db" 创建并启动消费者:`kafka_consumer_task_by_binding("binlog-db", binlog_handler)`。
- 处理器位置与逻辑: 提供 `binlog_handler`、`deal_binlog`、`canal_handle_message`、`event_notify_matching`(与 Kafka 通用封装分离)。
- `binlog_handler`:按 Canal 风格 JSON 反序列化,识别 INSERT/UPDATE/DELETE,构造统一记录并调用 `deal_binlog`。
- `deal_binlog`:先调用 `canal_handle_message`(可替换为你的业务处理),再调用 `event_notify_matching` 触发事件路由。
- 配置要求:
- 必填:`KAFKA_BOOTSTRAP_SERVERS`
- 绑定:默认内置 `binlog-db`(destination=`binlog-db`, content-type=`application/json`, group=`low-engine_test1`),group 可通过 `KAFKA_BINDING_BINLOG_DB_GROUP` 覆盖,或使用 `KAFKA_BINDINGS_JSON` 批量声明。
- 关闭/禁用自动消费:如不希望在启动时自动消费,可在 `app.py` 的 lifespan 启动阶段注释对应代码块。
## Kafka 配置与绑定(对齐 Spring Cloud Stream 思路)
为对齐 Spring Cloud Stream 的 binder/bindings 思路,项目将 Kafka 连接参数与 Topic 绑定抽象为统一配置,并在 Manager/Service 层提供“按绑定名”进行生产与消费的能力。
- 配置模块:`api/config/kafka_config.py`
- KafkaSettings:包含 `binder`(连接层)与 `bindings`(绑定定义)
- binder(连接层)参数:
- `KAFKA_BOOTSTRAP_SERVERS`(必需):Kafka 集群地址(示例:`10.10.1.52:9092,10.10.1.5:9092,10.10.1.178:9092`)
- `KAFKA_REPLICATION_FACTOR`(可选,默认 1):副本因子(参考 spring.cloud.stream.kafka.binder.replication-factor)
- `KAFKA_AUTO_CREATE_TOPICS`(可选,默认 true):是否允许自动创建主题(参考 spring.cloud.stream.kafka.binder.auto-create-topics)
- `KAFKA_ENABLED`(可选,默认 false):是否启用 Kafka(测试或本地无 Kafka 时可关闭,关联测试自动跳过)
- bindings(绑定定义):
- 支持通过 `KAFKA_BINDINGS_JSON` 批量声明绑定,示例:
```json
{
"binlog-db": {
"destination": "binlog-db",
"group": "low-engine_test1",
"content_type": "application/json"
}
}
```
- 也支持针对单个绑定使用环境变量覆盖:
- `KAFKA_BINDING_BINLOG_DB_GROUP`(覆盖绑定 `binlog-db` 的 group)
- `KAFKA_BINDING_BINLOG_DB_CT`(覆盖绑定 `binlog-db` 的 content-type)
- Spring 配置映射关系:
- `spring.cloud.stream.kafka.binder.brokers` -> `KAFKA_BOOTSTRAP_SERVERS`
- `spring.cloud.stream.kafka.binder.replication-factor` -> `KAFKA_REPLICATION_FACTOR`
- `spring.cloud.stream.kafka.binder.auto-create-topics` -> `KAFKA_AUTO_CREATE_TOPICS`
- `spring.cloud.stream.bindings..destination` -> `KAFKA_BINDINGS_JSON` 的 `destination` 或默认值
- `spring.cloud.stream.bindings..group` -> `KAFKA_BINDINGS_JSON` 的 `group` 或 `KAFKA_BINDING__GROUP`
- `spring.cloud.stream.bindings..content-type` -> `KAFKA_BINDINGS_JSON` 的 `content_type` 或 `KAFKA_BINDING__CT`
- 绑定驱动的生产/消费(Service 层方法):
- 生产:
- 发送 JSON:`kafka_send_by_binding_json("binlog-db", {"id": 1, "op": "update"})`
- 发送字节:`kafka_send_by_binding_bytes("binlog-db", b"raw-bytes")`
- 消费(可作为 BackgroundTasks):
- 创建绑定消费任务:`task = kafka_consumer_task_by_binding("binlog-db", handler)`
- 其中 `handler` 为异步函数,若绑定的 `content_type=application/json` 则消费者会自动进行 JSON 解码
- 模块位置(供参考):
- 配置:`api/config/kafka_config.py`、`api/config/__init__.py`
- 管理器:`api/managers/kafka_manager.py`(读取配置,提供按绑定名生产与消费的能力)
- 服务:`api/services/kafka_service.py`(导出绑定级别的便捷方法)
- 生命周期与清理:
- 应用关闭时(FastAPI shutdown)会调用 `KAFKA_MANAGER.close()`,统一关闭 Producer/Consumer,避免资源泄露(见 `api/app.py`)
- 可选增强:
- 如需在启动时自动创建缺失的 Topic 并设置分区/副本,项目已提供 Kafka Admin 客户端的自动创建能力(`KafkaManager._ensure_topic`)。
## Redis 集成与使用
- 依赖:已在 的 dependencies 中加入 `redis>=5.0.0`
- 配置:通过环境变量控制连接参数(`.env` 示例):
```env
REDIS_ENABLED=true
REDIS_HOST=r-wz9og1vhqw2witfy9ipd.redis.rds.aliyuncs.com
REDIS_PORT=16379
REDIS_PASSWORD=********
REDIS_DB=0
REDIS_SSL=false
```
- 模块位置:
- 配置:
- 管理器:
- 使用方式:
- 获取客户端单例:`from api.managers.redis_manager import REDIS_MANAGER`
- 基础操作:
- 连接探测:`await REDIS_MANAGER.ping()`
- 写入字符串:`await REDIS_MANAGER.set_str("key", "value", ex=60)`
- 读取字符串:`await REDIS_MANAGER.get_str("key")`
- 写入 JSON:`await REDIS_MANAGER.set_json("obj:k", {"a": 1}, ex=60)`
- 读取 JSON:`await REDIS_MANAGER.get_json("obj:k")`
- 注意:
- 若服务端不支持 TLS 或端口为明文,应设置 `REDIS_SSL=false`(本项目单元测试在开启 TLS 时遇到 `WRONG_VERSION_NUMBER/UNEXPECTED_EOF`,关闭后通过)。
- 请勿将密码明文提交至版本库,推荐通过 `.env` 与部署平台的密钥管理加载。
### Redis 使用示例(简化封装)
- 模块级便捷方法(同步调用,无需 await):
```python
from api.managers.redis_manager import (
redis_set, redis_get, redis_delete, redis_exists, redis_get_or_set,
)
```
- 字符串读写(支持 TTL 秒级过期):
```python
# 写入字符串,设置 60 秒过期
redis_set("demo:k1", "hello", ttl_seconds=60)
# 读取字符串(不存在返回 None)
val = redis_get("demo:k1")
```
- JSON 读写(自动序列化/反序列化,支持 TTL):
```python
redis_set("demo:user:1", {"id": 1, "name": "Alice"}, ttl_seconds=120)
obj = redis_get("demo:user:1") # -> {"id": 1, "name": "Alice"}
```
- 简易缓存模式(get_or_set):
```python
def load_user_42():
# 这里替换成你的真实数据源读取
return {"id": 42, "name": "Bob"}
user = redis_get_or_set("cache:user:42", load_user_42, ttl_seconds=300)
# 若 key 不存在,执行 factory 计算并写入;若存在,直接返回缓存
```
- 检查与删除:
```python
exists = redis_exists("demo:k1") # -> True/False
deleted = redis_delete("demo:k1") # -> 删除的键数量(int)
```
- 键命名建议:
- 使用命名空间风格:`app:module:resource:id`,例如:`fe:flows:run:123`。
- 对缓存类 key 设置合理 TTL,避免长期占用内存;对象类数据建议使用 JSON 存储。
- 避免缓存敏感信息(密码、token 等);如需临时缓存,务必设置 TTL。
- 兼容性说明:
- 仍可使用原有方法:`REDIS_MANAGER.set_str/get_str/set_json/get_json`。
- 当传入 dict/list 时,`redis_set` 自动序列化为 JSON;其它类型以字符串存储。
- `redis_get` 会先尝试按 JSON 反序列化,失败则返回原始字符串。
- 为 JSON 设置 TTL 时,内部通过 `expire(key, seconds)` 实现;字符串写入的 TTL 通过 `ex` 参数完成。
## 测试与验证(使用 uv 管理依赖)
- 安装测试依赖(建议使用 uv 可选依赖机制):
- 安装:`uv pip install -e .[test]`
- 或直接运行:`uv run -m pytest -q`
- Kafka 集成测试(真实连接 Kafka):
- 位置:`tests/test_kafka.py`
- 环境变量对应关系(测试会自动设置):
- `KAFKA_BOOTSTRAP_SERVERS=10.10.1.52:9092,10.10.1.5:9092,10.10.1.178:9092`
- `KAFKA_REPLICATION_FACTOR=1`
- `KAFKA_AUTO_CREATE_TOPICS=true`
- `KAFKA_BINDING_BINLOG_DB_GROUP=`
- 运行示例:
```bash
uv run -m pytest tests/test_kafka.py -q
```
- 测试说明:
- `test_kafka_producer_send_json_and_consume_real`:真实 Producer 发送 JSON 到绑定 `binlog-db`,真实 Consumer 在同一 group 中消费并断言收到消息
- `test_kafka_consumer_task_by_binding_real`:通过绑定创建真实消费任务并消费真实 Producer 发送的消息
- `test_stop_consumer_not_found`:验证停止不存在的消费者时返回 False
- 跳过策略:
- 当 `KAFKA_ENABLED=false` 时,整个 `tests/test_kafka.py` 会在模块级跳过,避免无 Kafka 环境下的失败。
- Redis 集成测试(真实连接 Redis):
- 位置:`tests/test_redis.py`
- 运行示例:
```bash
uv run -m pytest tests/test_redis.py -q
```
- 测试说明:
- `test_redis_connectivity`:验证连接与 `PING`
- `test_redis_set_get_str`:验证字符串写入/读取
- `test_redis_set_get_json`:验证 JSON 写入/读取
- 跳过策略:
- 当 `REDIS_ENABLED=false` 时测试自动跳过;若启用 TLS 失败,请设置 `REDIS_SSL=false` 并重试。
- 全量测试:
```bash
uv run -m pytest -q
```
- 其他:
- FastAPI 已迁移至 lifespan 事件处理(),避免弃用警告;并通过导入字符串 `api.app:app` 支持热重载。
## 执行上下文与 Redis 规范(对齐 dify/n8n/langFlow 思路)
为满足“除 Flow 配置外,还需包含触发入参,并将每个节点的输出写入执行上下文,格式化为 JSON 写入 Redis,供后续节点复用”的诉求,约定如下:
- 键命名:`#{flowRecordId}_context`,其中 `flowRecordId` 即运行记录 ID(DB: runs.id)。
- 写入时机:
- 运行开始:写入初始上下文(含触发事件等)。
- 每个节点执行完成后:写入最新快照(包含该节点的标准化输出)。
- 运行结束:再次写入最终上下文(含 visited_steps、trace 等持久化到 DB)。
- 上下文 Schema(JSON):
```json
{
"user": {"id": "optional"},
"request": {"source": "optional"},
"event": {
"eventCode": "REGISTER_ACCOUNT",
"eventName": "Partner Registered",
"eventSource": "fuse pro",
"data": {"email": "alice@example.com", "name": "Alice"}
},
"flow": {"id": 123, "engine": "flow"},
"nodes": {
"": {
"id": "",
"type": "",
"input": {"...": "节点输入(可选)"},
"output": {"...": "节点输出(标准化)"},
"ts": "YYYY-MM-DD HH:mm:ss"
}
},
// 更新示例:移除 last_http,全量以 nodes..output 为引用
{
"nodes": {
"n_http_1": {
"output": { "statusCode": 200, "headers": {"...": "..."}, "body": "..." }
}
}
}
}
```
- 设计理念:
- 触发上下文:Start 节点支持 `triggerType=event`,需在 `context.event` 中提供与配置匹配的事件;该事件来自 binlog 解析后生成的业务对象(例如 RegisterAccountEvent),在此以 JSON 表示。参考 n8n/Dify 的触发器节点设计。
- 节点输出标准化:每个节点执行后将其 `input` 与 `output` 写入 `context.nodes[]`,保证后续节点能直接复用。类似于 langFlow 的“state graph”中将消息/变量写入共享状态。
- Redis 快照:每步将最新 `context` 写入 Redis(键:`#{run_id}_context`),便于其他系统或节点按需读取最新状态(例如下游外部执行器或并行子流程)。
### 最简示例与验证步骤
示例:`examples/http_flow.json`(HTTP 节点将响应写入 `nodes.n_http_1.output`)。
1) 创建并运行 Flow(通过 Service 层,自动生成 run_id 并写入 Redis):
```python
from flow_engine.io.loader import FlowLoader
from flow_engine.core.graph import FlowGraph
from api.services.flows_service import run_flow_service, create_flow_service
from api.dao.flows_dao import get_flow_by_id
# 1. 从示例加载并入库
flow_def = FlowLoader.load_from_json_file("examples/http_flow.json")
flow_id = create_flow_service("http_flow_demo", flow_def)
# 2. 触发上下文(模拟事件/请求)
context = {
"event": {"eventCode": "HTTP_DEMO", "eventSource": "local", "data": {"q": "ping"}},
"request": {"source": "readme"},
}
# 3. 运行(默认 engine=flow),返回 run_id 和执行结果
run_id, result = run_flow_service(flow_id=flow_id, engine="flow", context=context)
print("run_id=", run_id)
print("trace size=", len(result.get("trace", [])))
```
2) 验证 Redis 上下文:
```python
from api.managers.redis_manager import REDIS_MANAGER
ctx = REDIS_MANAGER.get_json(f"{run_id}_context")
print(ctx.get("nodes"))
预期:
- `ctx['event']` 为传入的触发事件。
- `ctx['nodes']` 包含每个运行过节点的 `input/output/ts`,可作为后续节点的输入来源。
### 参考与对齐
- 触发器/上下文传递:对齐 n8n/Dify 的触发器与变量传递理念,节点之间通过共享上下文(state)传递数据。
- 状态图/快照:参考 LangGraph 的状态图思想,每步持久化最新上下文,便于并行与外部编排器读取。
注意:
- 若需要将业务对象(如 RegisterAccountEvent)直接存入上下文,建议序列化为 JSON(可含 `type` 与 `payload` 字段),或在节点层按需转换,避免不可序列化对象导致持久化失败。
- Redis 键 TTL 可在 `REDIS_MANAGER.set_json` 层按需设置(当前默认无过期)。