# spring-ai-alibaba-demo **Repository Path**: createmaker/spring-ai-alibaba-demo ## Basic Information - **Project Name**: spring-ai-alibaba-demo - **Description**: spring ai alibaba with hermes , agentic rag , obsidian - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-05-03 - **Last Updated**: 2026-07-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # spring-ai-alibaba-demo > Spring AI 1.0 + Agentic RAG 企业可用参考实现 —— 9 个 REST 端点 / 7 步 Agent 链路 / > 全套可观测性 / 安全限流 / 多副本就绪。 [English README](README-EN.md) --- ## 一、能力全景图 ``` ┌──────────────────── 7 个 Tab 演示界面(/)──────────────────┐ │ 1 chat │ 2 stream │ 3 memory │ 4 tools │ 5 rag │ 6 agentic-rag │ 7 ingest │ └──────────────────────────────────────────────────────────┘ ↓ ┌──────────────────── 9 个 REST 端点 ──────────────────────┐ │ POST /api/chat │ │ GET /api/chat/stream │ │ POST /api/chat/{conversationId} │ │ POST /api/chat/tools │ │ POST /api/chat/rag │ │ POST /api/chat/agentic-rag ← 同步 │ │ GET /api/chat/agentic-rag/stream ← SSE 流式 │ │ POST /api/rag/docs ← 文本入库 │ │ POST /api/rag/docs/upload ← 文件上传 │ │ GET /api/rag/docs ← 列表 │ │ DELETE /api/rag/docs/{source} ← 按 source 删除 │ │ DELETE /api/rag/docs ← 清空 │ │ GET /api/rag/docs/stats ← 统计 │ │ GET /actuator/health /metrics /prometheus ... │ └──────────────────────────────────────────────────────────┘ ↓ ┌──────────────── Agentic RAG 7 步链路 ────────────────────┐ │ 0 历史改写 (multi-turn) │ │ 1 查询规划/分解(LLM 决策) │ │ 2 多路检索 + LLM 文档评分 │ │ 2b 失败时改写查询重试 │ │ 2.5 LLM Reranker 二阶段精排 │ │ 3 答案生成 │ │ 4 答案质量评估(grounded + addressesQuestion) │ │ 5 不达标时严格模式重生成(自我纠错) │ │ ↓ 每步通过 SSE 实时推送事件 │ │ ↓ 每个 LLM 调用走 LlmInvoker(重试/超时/降级/指标/span)│ └──────────────────────────────────────────────────────────┘ ↓ ┌──────────── 治理 & 可观测 & 多副本 ──────────────────────┐ │ • TraceIdFilter → MDC traceId 串联多步 │ │ • LlmInvoker → 重试 / 超时 / 降级 / Micrometer / Tracer │ │ • PromptLoader → prompts/agentic-rag/*.txt 启动 fail-fast│ │ • Actuator + Prometheus + OTLP tracing │ │ • API Key 鉴权(X-API-Key)+ Bucket4j 限流 │ │ • profile=chroma → Chroma 持久化向量库 │ │ • profile=redis → Redis 共享 ChatMemory + 分布式限流 │ └──────────────────────────────────────────────────────────┘ ``` --- ## 二、5 分钟启动 ```bash # 1) 默认(最小依赖:纯内存,HashEmbedding,连本地 Hermes/Ollama) mvn spring-boot:run # → http://localhost:8866 # 2) Docker 单容器 docker compose up -d --build # 3) Docker + Chroma 持久化向量库 docker compose --profile chroma up -d --build # 配合:spring-boot 启动加 --spring.profiles.active=chroma # 4) 全套可观测性(Prometheus + Grafana + Tempo) docker compose --profile observability up -d # Grafana → http://localhost:3000 admin / admin # 已预置 Agentic RAG dashboard,自动连 Prometheus + Tempo # 5) 一切都来(chroma + redis 多副本就绪 + 观测栈) docker compose --profile chroma --profile redis --profile observability up -d ``` --- ## 三、LLM 后端接入(Hermes Gateway / Ollama / DashScope / 真 OpenAI) > 本项目用 OpenAI 兼容协议跟 LLM 通信。**任何兼容 `/v1/chat/completions` 的服务都能接** —— > Hermes Gateway、Ollama、LM Studio、vLLM、阿里云百炼、真 OpenAI 都行。 > 下面按使用频率从高到低讲清接入方式 + 常见坑。 ### 3.1 三个核心配置 业务代码不关心后端是谁,全部通过这 3 个配置项决定: | 项 | 默认值(`application.yml`) | 含义 | |---|---|---| | `spring.ai.openai.base-url` | `${LLM_BASE_URL:http://localhost:8642}` | LLM HTTP 入口(**不带** `/v1`,Spring AI 自动追加 `/v1/chat/completions`)| | `spring.ai.openai.api-key` | `${LLM_API_KEY:unused}` | Bearer token(无认证服务可填任意非空字符串)| | `spring.ai.openai.chat.options.model` | `${LLM_MODEL:hermes-agent}` | 模型名(必须是后端支持的,例如 `hermes-agent` / `qwen2.5:3b` / `gpt-4o-mini`) | **三种注入方式**,优先级从高到低: 1. **环境变量** —— `LLM_BASE_URL` / `LLM_API_KEY` / `LLM_MODEL`(容器/K8s 推荐) 2. **`application-local.yml`** —— 本机开发期沉淀(gitignore 保护) 3. **`application.yml`** —— 默认兜底 ```yaml # 本机开发:在 src/main/resources/application-local.yml 写一份永久配置 # 此文件已加入 .gitignore,不会进 git 历史 spring: ai: openai: base-url: http://localhost:8642 api-key: bba568...e6f8d chat: options: model: hermes-agent ``` 启动时 `application.yml` 通过 `spring.config.import: optional:classpath:application-local.yml` **自动**加载这份文件(不存在不报错)。每次重启不用 set env,IntelliJ 双击启动也生效。 ### 3.2 接入 Hermes Gateway(OpenAI 兼容自建服务) Hermes 是常见的本地 LLM 自建网关,OpenAI 协议兼容。 #### 3.2.1 网关侧配置参考 ``` | 项 | 值 | |---|---| | 监听 IP | 0.0.0.0(必须!见 3.2.3 WSL 坑) | | 端口 | 8642 | | API Key | 自行生成(如 32-byte 随机 hex) | | 协议 | HTTP/1.1,OpenAI Chat Completions API | ``` #### 3.2.2 Spring 侧配置 ```yaml # application-local.yml spring: ai: openai: base-url: http://localhost:8642 api-key: <你的 hermes api key> chat: options: model: hermes-agent # 你部署的模型名 ``` 或环境变量(一次性): ```powershell $env:LLM_BASE_URL = "http://localhost:8642" $env:LLM_API_KEY = "bba568...e6f8d" $env:LLM_MODEL = "hermes-agent" mvn spring-boot:run ``` #### 3.2.3 ⚠️ WSL2 网络坑(最常踩) **症状**:Spring Boot 报 `Connection refused: localhost:8642`,但 WSL 里 `curl localhost:8642` 明明能通。 **根因**:WSL2 自动 localhost 转发**只对绑 `0.0.0.0` 的服务生效**。Hermes 默认绑 `127.0.0.1`("仅本机访问")时,Windows 宿主机这条 localhost 路径根本到不了 WSL 里。 **修法**:把 Hermes 改绑到 `0.0.0.0`: ```bash # 在 WSL 里 sudo systemctl cat hermes | grep ExecStart # 找到启动命令 # 改 --host 127.0.0.1 → --host 0.0.0.0(位置因 Hermes 版本而异) sudo systemctl daemon-reload sudo systemctl restart hermes # 验证 ss -tlnp | grep 8642 # 期望:LISTEN 0.0.0.0:8642 (而不是 127.0.0.1:8642) ``` **安全说明**:绑 `0.0.0.0` 在 WSL2 里**不等于暴露公网**。WSL2 默认网络隔离,只有 Windows 宿主机能通过 localhost 转发到达,局域网其他机器和公网都不能直接访问(除非你在 Windows 防火墙额外开洞)。 #### 3.2.4 三步连通性验证 ```powershell # 1. 网关本身可达(Windows 宿主机直连) curl.exe -H "Authorization: Bearer " http://localhost:8642/v1/models # 期望:HTTP 200,返回模型列表 JSON # 2. Spring Boot 启动后健康 curl.exe http://localhost:8866/actuator/health # llm 组件应该 UP(如果 DOWN 看 details.error) # 3. 跑一次完整链路 curl.exe -H "content-type:application/json" --data-binary '{\"input\":\"hello\"}' http://localhost:8866/api/chat # 期望:HTTP 200,返回 LLM 实际回复 ``` ### 3.3 接入 Ollama(推荐离线本地路线) Ollama 是最简单的本地 LLM 部署方式,OpenAI 兼容端点开箱可用。 ```powershell # 1. 装 Ollama(一次性) # 下载安装包:https://ollama.com/download/OllamaSetup.exe(约 700MB) # 2. 拉模型(约 2GB) ollama pull qwen2.5:3b # 中文好 # 或 ollama pull llama3.2:3b # 英文好 # 3. 启动(默认监听 11434) ollama serve # 4. 本项目接入 ``` ```yaml # application-local.yml spring: ai: openai: base-url: http://localhost:11434 # Ollama 默认端口 api-key: ollama # Ollama 不校验,任意非空即可 chat: options: model: qwen2.5:3b ``` ### 3.4 接入阿里云百炼(DashScope) ```yaml # application-local.yml spring: ai: openai: base-url: https://dashscope.aliyuncs.com/compatible-mode api-key: sk-你的-DashScope-key chat: options: model: qwen-turbo # 或 qwen-plus / qwen-max / qwen3-... ``` ### 3.5 接入真 OpenAI ```yaml # application-local.yml spring: ai: openai: base-url: https://api.openai.com api-key: sk-你的-OpenAI-key chat: options: model: gpt-4o-mini ``` ### 3.6 故障排查(按错误信息) | 错误信息 | 根因 | 解决 | |---|---|---| | `Connection refused: localhost:8642` | 后端没起,或绑了 127.0.0.1 而你是 Windows ↔ WSL 跨界 | 起后端 / 改绑 0.0.0.0(见 3.2.3)| | `HTTP 401 Invalid API key` | api-key 没传 / 失效 | 检查 application-local.yml 里 api-key 是否最新 | | `HTTP 404 model not found` | 模型名拼错或后端没装 | 调 `/v1/models` 看支持哪些;改 `chat.options.model` | | `HTTP 429 Too Many Requests` | 后端限流 | 降低 `llm.invoker.max-attempts` 或加大 `initial-backoff-ms` | | 启动时 `corpusLoader: Connection refused /v1/embeddings` | Spring AI 把你的 chat 服务当成 embedding 服务调了 | 已在 application.yml 里 `spring.ai.model.embedding: none` 关掉,本项目用本地 HashEmbedding | | Spring Boot 起来但 health 显示 `llm DOWN` | 第 1 次 LLM 探测超时(默认 8s)| 不影响业务,调用一次 chat 后下次探测就 UP | ### 3.7 请求 LLM 时的内部链路 ``` Tab 6 Agentic RAG / Tab 1 Chat │ ▼ AgenticRagController / ChatController │ ▼ LlmInvoker.invoke("queryPlan", () -> chatClient.prompt()...) ★ 治理层 │ ↓ 失败时按 maxAttempts=3 + 指数退避重试 │ ↓ 超时 60s 强制中断 │ ↓ Micrometer Timer 记录延迟 │ ↓ Tracer 创建 span "llm.queryPlan" ▼ Spring AI ChatClient │ ▼ OpenAiApi.chatCompletionEntity(...) │ ↓ POST {base-url}/v1/chat/completions │ ↓ Header: Authorization: Bearer {api-key} ▼ Hermes Gateway / Ollama / DashScope ... ``` 每次 LLM 调用产生: - **日志**:`[llm:queryPlan] success in 1234ms`(带 traceId) - **指标**:`llm.invoker.duration{operation=queryPlan, outcome=success}` - **trace span**:`llm.queryPlan` 子 span(如果 OTLP 接收端在) --- ## 四、部署能力矩阵 | 能力 | 默认 | profile=chroma | profile=redis | profile=observability | 同时全开 | |---|:-:|:-:|:-:|:-:|:-:| | 9 个 REST 端点 | ✅ | ✅ | ✅ | ✅ | ✅ | | Agentic RAG 7 步链路 | ✅ | ✅ | ✅ | ✅ | ✅ | | 文档运行时入库 | ✅ | ✅ | ✅ | ✅ | ✅ | | 启动时加载 docs/*.md | ✅ | ✅ | ✅ | ✅ | ✅ | | **持久化向量库** | ❌ 内存 | ✅ Chroma | ❌ | ❌ | ✅ | | **多副本会话共享** | ❌ 内存 | ❌ | ✅ Redis | ❌ | ✅ | | **多副本限流(分布式)** | ❌ 单机 | ❌ | ✅ Redis | ❌ | ✅ | | **Prometheus 指标** | ✅ 暴露 | ✅ | ✅ | ✅ 抓取 | ✅ | | **OTLP tracing** | ✅ 暴露 | ✅ | ✅ | ✅ Tempo | ✅ | | **Grafana dashboard** | ❌ | ❌ | ❌ | ✅ | ✅ | --- ## 四.5、Tier 4 增强(最新) 在 Tier 1/2/3 基础上,加了 6 项进阶能力: | 项 | 能力 | 触发位置 | |---|---|---| | **T4.9** | Swagger / OpenAPI UI | 启动后访问 `/swagger-ui.html` | | **T4.7** | Token 用量追踪 + 预算熔断 | `llm.budget.enabled=true` + `/api/admin/budget` | | **T4.17** | Prompt Injection 防御(输入清洗 + 文档投毒过滤)| Agentic RAG 入口自动跑 | | **T4.5** | 子查询并行检索(3 子查询从 ~3s → ~1s)| Agentic RAG Step 2 | | **T4.1** | BM25 + 向量混合检索(RRF 融合)| Agentic RAG Step 2 | | **T4.13** | Self-RAG 路由(闲聊跳过检索) | Agentic RAG Step 0.5 | 新增 Agentic RAG 流程图(带 Tier 4 步骤标记): ``` 用户问题 ↓ [Step -1] T4.17 PromptGuard 输入安全检查(拦截 injection) ★ 新 ↓ [Step 0] 历史改写(多轮) ↓ [Step 0.5] T4.13 Self-RAG 路由(闲聊→直答;业务→走 RAG) ★ 新 ↓ [Step 1] 查询规划(拆子查询) ↓ [Step 2] T4.5 并行 + T4.1 BM25+向量混合 RRF 检索 ★ 新 ↓ T4.17 文档投毒过滤 [Step 2.5] LLM Reranker 精排 ↓ [Step 3] 答案生成 ↓ [Step 4] 答案质量评估 ↓ [Step 5] 严格模式重生成(自我纠错) ↓ 返回结果 ``` ### 新增端点 | 方法 | 路径 | 说明 | |---|---|---| | GET | `/swagger-ui.html` | OpenAPI 交互式 API 浏览器 | | GET | `/v3/api-docs` | OpenAPI JSON spec(导入 Postman/Bruno) | | GET | `/api/admin/budget` | 当前 token 预算用量 + 上限 | | GET | `/actuator/metrics/llm.tokens.prompt` | 累计 prompt token | | GET | `/actuator/metrics/llm.tokens.completion` | 累计 completion token | | GET | `/actuator/metrics/llm.cost.usd` | 估算累计美元成本 | ### Token 预算配置 ```yaml llm: budget: enabled: true # 默认 false max-tokens-per-minute: 100000 # 0 = 不限 max-tokens-per-day: 10000000 cost-per-1k-prompt: 0.001 # USD,自定义价格 cost-per-1k-completion: 0.002 ``` 启用后任何 LLM 调用前会检查;超限抛 `TokenBudgetExceededException`,被 LlmInvoker 捕获走 fallback,**不会让 HTTP 请求 500**。 --- ## 五、关键 Spring AI 用法对照(同一能力,框架前后) | 能力 | 手写版的复杂度 | 本项目 | |---|---|---| | 基础 chat | LLMClient 包 messages + HTTP + 解析 ~50 行 | `chatClient.prompt().user(x).call().content()` 一行 | | 流式 SSE | worker thread + 队列 ~80 行 | `chatClient.prompt().stream().content()` → `Flux` | | 多轮记忆 | Conversation + 摘要压缩 ~150 行 | `MessageWindowChatMemory` + `MessageChatMemoryAdvisor` | | 工具调用 | system prompt 描述 + JSON dispatch ~100 行 | `@Tool` 注解 + `chatClient.tools(obj)` | | 普通 RAG | BM25Retriever + observation ~200 行 | `QuestionAnswerAdvisor` + `SimpleVectorStore` | | **Agentic RAG** | 不存在 | 本项目自实现:LlmInvoker + PromptLoader + 7 步编排 | --- ## 六、所有可调开关(环境变量) > 配置优先级:**环境变量 > `application-local.yml` > `application.yml`**。 > 本机开发优先用 `application-local.yml`(已 gitignore),CI/Docker/K8s 用环境变量。 > 接入细节见 [§ 三 LLM 后端接入](#三llm-后端接入hermes-gateway--ollama--dashscope--真-openai)。 | 变量 | 默认 | 作用 | |---|---|---| | `LLM_BASE_URL` | `http://localhost:8642` | OpenAI 兼容 LLM 入口(Hermes/Ollama/真 OpenAI/DashScope) | | `LLM_API_KEY` | `unused` | LLM API key(Hermes/真 OpenAI 必须;Ollama 任意非空即可) | | `LLM_MODEL` | `hermes-agent` | 模型名(须为后端实际部署的模型)| | `SPRING_PROFILES_ACTIVE` | (空) | 多个用逗号:`chroma,redis` | | `SECURITY_API_KEY_ENABLED` | `false` | 启用 X-API-Key 鉴权 | | `SECURITY_API_KEYS` | (空) | 多个 key 逗号分隔 | | `SECURITY_RATE_LIMIT_ENABLED` | `false` | 启用 Bucket4j 限流 | | `SECURITY_RATE_LIMIT_CAPACITY` | `60` | 桶容量(单 key/IP 每周期最大请求数) | | `SECURITY_RATE_LIMIT_REFILL_TOKENS` | `60` | 每周期补发 token 数 | | `SECURITY_RATE_LIMIT_REFILL_PERIOD` | `60` | 补发周期(秒) | | `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4318/v1/traces` | OTLP tracing 端点(Tempo / Jaeger) | | `TRACING_SAMPLE_RATE` | `1.0` | tracing 采样率 | | `CHROMA_HOST` / `CHROMA_PORT` | localhost / 8000 | Chroma server(profile=chroma) | | `REDIS_HOST` / `REDIS_PORT` / `REDIS_PASSWORD` | localhost / 6379 / 空 | Redis(profile=redis) | | `CHAT_MEMORY_TTL_HOURS` | `24` | Redis 中会话历史的过期时间 | --- ## 七、Agentic RAG 流式事件协议(SSE) ``` GET /api/chat/agentic-rag/stream?input=...&conversationId=... ``` 11 类事件类型,前端按需渲染: | event name | data 关键字段 | 触发时机 | |---|---|---| | `start` | `query`, `conversationId` | 处理开始 | | `history-rewrite` | `from`, `to` | 多轮场景下问题改写 | | `plan` | `decomposed`, `subQueries`, `reasoning` | 查询规划完成 | | `retrieve` | `subQuery`, `candidateCount` | 一次向量检索 | | `grade-docs` | `subQuery`, `relevantCount` | LLM 文档评分完成 | | `rewrite` | `from`, `to` | 改写查询重试 | | `rerank` | `before`, `after`, `rankedIndexes` | LLM 二阶段精排 | | `generate` | `strict`, `docCount`, `answer` | 答案生成完成 | | `grade-answer` | `grounded`, `addressesQuestion`, `reasoning` | 答案质量评估 | | `retry` | `reason` | 触发严格模式重生成 | | `done` | `result`(完整 AgenticRagResult) | 全部完成 | | `error` | `error` | 不可恢复异常 | --- ## 八、关键源码索引 | 主题 | 文件 | |---|---| | Agent 编排核心 | [`agentic/AgenticRagService.java`](src/main/java/com/agentic/sai/agentic/AgenticRagService.java) | | LLM 治理层 | [`infra/LlmInvoker.java`](src/main/java/com/agentic/sai/infra/LlmInvoker.java) | | Prompt 外置加载 | [`infra/PromptLoader.java`](src/main/java/com/agentic/sai/infra/PromptLoader.java) | | Prompt 内容 | [`resources/prompts/agentic-rag/*.txt`](src/main/resources/prompts/agentic-rag/) | | traceId 注入 | [`infra/TraceIdFilter.java`](src/main/java/com/agentic/sai/infra/TraceIdFilter.java) | | 健康检查 | [`infra/health/`](src/main/java/com/agentic/sai/infra/health/) | | 鉴权 / 限流 | [`infra/security/`](src/main/java/com/agentic/sai/infra/security/) | | Redis 多副本 | [`infra/redis/`](src/main/java/com/agentic/sai/infra/redis/) | | 文档管理 | [`ingest/DocumentIngestService.java`](src/main/java/com/agentic/sai/ingest/DocumentIngestService.java) | | RAG 配置(含 Chroma 切换说明) | [`config/RagConfig.java`](src/main/java/com/agentic/sai/config/RagConfig.java) | --- ## 九、生产部署 checklist - [ ] 把 `LLM_API_KEY` 换成生产 key(DashScope / OpenAI),不要明文写在 yml - [ ] 启用真 Embedding:`spring.ai.openai.embedding.enabled=true` - [ ] 启用 `profile=chroma` 持久化向量库 - [ ] 多实例时启用 `profile=redis` 共享会话/分布式限流 - [ ] 启用 `SECURITY_API_KEY_ENABLED=true` + 配合 nginx/网关 mTLS - [ ] 启用 `SECURITY_RATE_LIMIT_ENABLED=true` - [ ] 部署 Tempo/Jaeger 接收 OTLP,配 `OTEL_EXPORTER_OTLP_ENDPOINT` - [ ] 部署 Prometheus 抓 `/actuator/prometheus`,告警规则配 P99 / fallback 计数 - [ ] Actuator 端点限制网络访问(生产只让 ops 网段进来) - [ ] Prompt 文件随版本发布,敏感修改走 PR 审查 --- ## 十、回归测试 ```bash mvn test # 22 个测试,覆盖 LlmInvoker 治理、文档去重、Hash embedding ```