于 Spring Boot 和 LangChain4j 的 Web 智能助手

LangChain4j Chat Agent

这是一个基于 Spring Boot 3 和 LangChain4j 的 Web 智能助手,支持流式聊天、模型配置切换、文档问答,以及 GitHub、网页抓取和受限文件系统工具。

运行环境

  • JDK 21 或更高版本
  • Maven 3.8 或更高版本
  • Ollama:使用本地聊天模型或文档问答时需要
  • Redis:上传文档并进行问答时需要
  • Node.js:使用 GitHub 工具时需要,项目会通过 npx 启动 MCP 服务
  • Python uvx:使用网页抓取工具时需要

仅使用 OpenAI、智谱或其他 OpenAI 兼容接口进行普通聊天时,不需要启动本地 Ollama;但上传文件仍需要 Ollama 嵌入模型和 Redis。

风格控制

输入

复制代码
畏惧告别 乔叶 我实际上是个十分口拙的人。而且,特别是在关键时刻尤为口拙。比如告别。 告别似乎是一个普遍公认的隆重时刻,也是一个最能够让人感怀的时刻。越是这样的时刻,我就越是畏惧。倒不是怕伤心怕落泪,而是怕说话。------人多还好,你一句我一句也容易混过。最怕人少,尤其是只有两个人的时候,无论语言的茅草多么丰盛,也总会有一些东西干巴巴地显露出来,让你不得不面对。 一位女友曾来看我。客观地说,她是那种满不错的朋友,只是不错也不错得挺一般,聊天聊得也还好。偶尔,我们也能够走到深处,但更多的时候,我们只是走在快乐的表面,一旦到了最孤独最无助的时候,我们第一个想到的人,都不是彼此。 她在我家住了两天。两天的相处使我们的友谊略有升温。而汽车站的告别却像催化剂一样莫名其妙地促进了我们对彼此的留恋。我们的话语顿时充满了巧克力一般甜蜜的浓香和油滑的关爱。 离开车时间还有十五分钟。 "有时间一定要再来玩啊。"我切切叮嘱。 "一定一定。"她殷殷答应,"你也一定要去我那里呀。" "一定一定。" 沉默。 "饿不饿?渴不渴?那边卖有吃的。"我笑道,"来这儿可别委屈。" "我才不生分呢。倒是你,该吃就吃,该喝就喝,别光顾着照顾我。"她忙说。 互相的谦让已经显出几分生分了。 "爸妈身体还好?"------忽然想起,这已经是我第三次问这个问题了。 "还好,"她的口气稍稍迟疑,"就是都有点儿高血压。" "那一定要注意呀。"我的心里一阵轻松。终于找到了一个合适的谈话点儿。------我的母亲曾经患过高血压,我在高血压方面的知识几乎等同于半个专业医生。 我们谈了足足有十二分钟的高血压。 发车时间终于到了。我们依依不舍地牵手,款款深情地揽肩,你给我掸尘土,我帮你抿头发,及尽了朋友间的肢体语言来为离别的氛围助兴。 时间到。有乘客催车。售票员不耐烦地解释:"还要再等上几分钟,有个人约好了要来坐这趟车的。" 只好接着聊。股票,电脑,广告,街上行人的穿着,单位同事的佚事,装修房子的得失。车,在我们纵横万里无所不及的云雾中终于发动起来了,于是我下车,她打开车窗,我们相对微笑。我挥手作别时,已觉辞尽。 车走了两步。突然间,又停了下来。原来是有人下来买烟。 我只好又赶上前。两人的表情重新开张。话语却如同剩茶续上了凉水,更加滋味惨淡。 "这个坤包颜色太深了,配你这个衣服不大好。" "有没有考虑去补照一套婚纱照?听说金艺影楼技术特好。" 买烟的人还在那里挑来挑去。真想替他买一包啊。 终于终于,汽车的烟尘笼罩了我。我在蒙蒙的视线里第三次和朋友作别。放下疲惫的手臂,我突然觉得有些窒息。在这一瞬间,我明白了好几件事情:人为什么会在许多时候变得虚伪;人为什么会在许多时候丧失勇气;人为什么会在许多时候深感生命的无聊和无奈;人为什么会在许多时候深陷平庸且支持平庸。 也终于明白:为什么只有真正的好友才会容许你相顾无语。为什么古人在造字时会把告别写做"辞",------那是"辛苦的舌头"啊。

输出

shell 复制代码
## 无限延迟的告别 车票上的发车时间是四点二十。候车厅的挂钟指向四点零五。 "一定要再来啊。"我说。 "一定一定。"她应道。 这是第一遍。我并不知道这是第一遍。 二 她的行李箱在我家地板上躺了两天。轮子上的泥干了,碎成细屑,嵌进浅色瓷砖的接缝里。我是在她走后才发现的。但在当时,在车站,我还在为终于找到话题而暗自庆幸。 "爸妈身体还好?"我问。 "还好,就是都有点儿高血压。" "那、那要当心。"我说。 我想再多说两句。可是话到了舌头尖上,又原路折了回去。我不是那种能顺着一个话题一路说下去的人。我更像一台慢热的旧风扇,转三下,停一下,再转。 好在她也习惯了。我们像两个手里都攥着空口袋的人,你看着我,我看着你,都指望对方先掏出点什么来。 "饿不饿?"我指指候车厅角落的零食摊,"那里有吃的。" "不饿,你吃。" "我也不饿。" 沉默。 沉默像一块浸了水的海绵,慢慢膨胀,挤掉了我们之间最后一点空隙。我看见她的视线游移,落在售票窗口旁边的一盆绿植上。那盆绿植叶子发黄,边角已经卷了起来。 离发车还有十二分钟。 "高血压,其实,"我听见自己的声音又起来了,"我、我妈妈以前也有。" "是吗。"她抬起头。 "嗯。就、就是要注意饮食,少盐,还有,还有运动,情绪也要、要稳定。"每说一个词,我都要在脑子里先把它攥住,再送出来。它们像一把散落的石子,一颗一颗往外扔,扔得断断续续。 她却接得平稳:"是,医生也这么说。" 在这段对话里,我们像两个合营的人,在一条没有风景的公路上并排骑着车。我负责蹬,她负责扶住车把。十二分钟。我们足足骑了十二分钟。 三 发车时间到了。 我们拥抱。她拍拍我的背。我的手臂在她肩胛骨上搭了一下,很快放下来。肢体语言是安全的,因为它不需要经过大脑的关卡。我们回到月台上,等车发动。 她隔着车窗向我挥手。我抬起手,掌心朝外,像在推一扇看不见的门。 车没有动。 售票员从车窗探出头:"再等几分钟,有人约好了要坐这趟车的。" 我重新走上前,心里像被针尖轻轻刺了一下。我好像经历过这个场面。不是刚才,是很久以前。不,也不对,就是刚才。可刚才我已经经历过一次了。 我甩了甩头。她在车窗里看着我,眼睛里有一层很薄的东西,像水面上的油渍,刚聚拢,又散开。 "你、你刚才------"我说了一半,停住了。 "什么?"她问。 "没什么。"我说,"那个......你那个包,颜色有点深。" "嗯,配这身衣服不太亮。"她应道。 她的声音也有点飘。像经过了一道很长的回廊。 售票员又喊话了:"再等两分钟,有人下车买烟去了。"她朝车尾指了指。我顺着方向看过去,一个中年男人正从车后门跳下车,小跑向候车厅门口的小卖部。他的灰色夹克下摆翻卷着,右手在裤兜里掏零钱,零钱掉了一枚,在他脚边弹了一下,滚进排水沟。他弯腰去捡,手指怎么也捏不住那枚硬币,像在一场梦里捉一条滑腻的鱼。 我转过脸。车窗里的她,也正望着那个买烟的男人。 "他刚才也掉过钱。"她说。 我愣住。"刚才?" "可能我看错了。"她的嘴唇动了动,像要把什么咽回去。 四 第二次延迟比第一次短。车发动时,我站在月台上,看着她再次抬起手,朝我挥了挥。我回了一个。但那种针尖刺过的感觉又来了,细密地、一下一下地扎我的后颈。 我转过身,准备离开。就在我迈出第三步的时候,一切又回到了原地。 四点零五。候车厅的挂钟。她的声音:"一定要再来啊。"------这次是从我嘴里说出来的。 我猛地把手插进口袋。指尖碰到裤袋里一颗扣子,很凉。我用力捏了捏,确认这是真的扣子,不是幻觉。可我明明已经送走了她两次。两次。车两次延迟。她两次隔着玻璃挥手。那个买烟的男人两次弯下腰捡硬币。 现在。第三次。 我站在同一个位置。她的脸从车窗里探出来,还是那个微笑,但这次她的额头上有汗,妆被冲淡了一点,眼角下的皮肤露出疲惫的底色。 "你一定也感觉到了。"她说。 她的声音很轻,像是怕打破一层紧贴着我们的玻璃。 我张了张嘴。舌头像一块木头。我本来想说"没有",或者"什么",但这些字刚冒上来,又被我吞了回去。我不能再用这些废话去填了。每一次,我就是这样,一句话一句话,把两个人之间的缝隙填满,填得那么用力,好像填得严丝合缝了,我们就安全了。 可每一次循环,都发生在我话说得最满的时候。 "我、我不太会说话。"我终于说。 "我知道。" "所以每次要送你走,我都怕。怕没话说。怕冷场。怕那个、那个间隙。"我的声音在抖,像一根拉过了头的皮筋,"所以我就一直说。说到自己都恶心。" 她点点头。她把车窗打开得更大一些。风从外面进来,刮走了我们之间那些悬浮太久的、轻飘飘的词语。 "我也一样,"她说,"我其实不想说那么多。可你不说,我就觉得应该由我来说。" "你不是我最亲近的人。"我没头没尾地冒出这么一句。 她顿了一下。 "你也不是我的。"她指了指自己胸口,"我们不是对方在半夜会打电话的人。" "对。"我的鼻子酸了一下。 "可我们还是愿意待两天。"她的声音轻下来,像谁把一块石头轻轻放回河底。 "对。" 这一次,我没有再去找下一句话。她也没有。我们隔着车窗坐着,让那些没有出口的字沉下去。站台上很吵。播报声、拉杆箱轮子声、远处有人讲电话。但那些声音都像在水面上,浮得很远。我们所在的水底,很静。 五 车发动了。 这一次是真的。引擎的震动从地面传上来,经过我的鞋底、膝盖、脊柱,一直到达头顶。像有人轻轻按了一只开关。 她缩回靠窗的座位里,放下手提包,把风衣下摆捋平。我站在月台上,手垂在身体两侧。我看见她抬手的动作有点像犹豫,手臂先是弯着,然后伸上来。我也抬起手。我们的掌心相对,隔着一层玻璃。她掌根抵在车窗上,五指慢慢张开。 风从站台尽头卷过来,带着油条和汽油的味道。烟尘扬起来,迷了一下眼睛。我没有说话。我怕一开口,这道好不容易才合拢的口子又会被我自己扯开。我让手掌贴紧袖子,坚持举着,直到车尾消失。 然后我放下手臂。 喉咙紧得像被人在外面系了结。我不得不张大嘴,做了一次很深的呼吸。空气涌进去,胀满了两片肺。我胸口发闷,像从很深的水底突然浮上来,耳朵里嗡地一声,世界重新变得锐利。那种闷,堵在喉结下方两寸的地方,重重地压着。我摸了摸那里。皮肤是烫的。舌头在口腔里动了动,像一块刚被放回原位的石头,还有一些泥土黏在上面。 我没有把它捋掉。 六 后来的很多次告别,我都会想起那个下午。 我站在那里,等车走远,等烟尘落下来,大张着嘴,像一条刚从鱼钩上脱身的鱼。空气燥热,灰尘在逆光里发白。我一下一下地吞咽,但喉结下方的那股闷意下不去,它不肯下去。我知道那是什么。是这么多年来,我一次又一次用舌头把它推出去的那些话,如今一并退了回来,堵在我的气管入口,逼我承认它们的重量。 我没有动。我等它们慢慢沉下去,像一杯浑水,静了,清了,最后看得见杯底的纹路。 而我学会了闭嘴。 像我这样的人,舌头是身体里最辛苦的器官。它一直替我做着一件最累的事:害怕沉默。 那天之后,我偶尔还是会怕。只是怕的时候,我学会了让舌头休息,让那些没说出的话,像烟尘一样,在两个人之间的地上,慢慢落下来。

快速启动

1. 使用本地 Ollama

先启动 Ollama 并准备模型:

bash 复制代码
ollama pull qwen2.5:7b
ollama pull quentinz/bge-large-zh-v1.5:latest
ollama serve

默认配置已经选择 ollama,直接启动项目:

arduino 复制代码
mvn spring-boot:run

也可以在 IntelliJ IDEA 中运行 com.melon.OpenManusApplication。

2. 使用云端模型

PowerShell 示例:

ini 复制代码
$env:LLM_PROVIDER = "openai"
$env:OPENAI_API_KEY = "你的 API Key"
$env:OPENAI_MODEL_NAME = "gpt-4o-mini"
mvn spring-boot:run

智谱模型:

ini 复制代码
$env:LLM_PROVIDER = "zhipu"
$env:ZHIPU_API_KEY = "你的 API Key"
$env:ZHIPU_MODEL_NAME = "glm-4"
mvn spring-boot:run

任意兼容 OpenAI Chat Completions 协议的服务:

ini 复制代码
$env:LLM_PROVIDER = "aihub"
$env:AIHUB_BASE_URL = "https://你的服务地址/v1"
$env:AIHUB_API_KEY = "你的 API Key"
$env:AIHUB_MODEL_NAME = "模型名称"
mvn spring-boot:run

也可以直接修改 src/main/resources/application.yml 中的 langchain4j.chat.provider。可选值为 ollama、openai、zhipu、aihub;切换后重启应用即可,无需修改 Java 代码。

使用方式

启动成功后访问 http://localhost:8898,在输入框中发送问题。回答通过 SSE 流式显示。

  • 普通聊天:直接输入问题。
  • 文档问答:上传 PDF、TXT、Markdown、HTML、Word、PPT 或 Excel 文件后再提问,单文件最大 10 MB、一次最多 3 个文件。
  • GitHub 查询:先设置 GITHUB_TOKEN,再询问仓库、提交或 Issue 等信息。
  • 网页分析:安装 uvx 后,在问题中包含要分析的网址。
  • 文件操作:默认只允许读取项目下的 data 目录,写入和删除默认关闭。

短文颠覆性改编工作流

项目提供了一套异步、多模型接力的短文改编工作流:

复制代码
原文分析 → 三角色头脑风暴 → 方向吸引力/网络热度评估 → 等待用户选择
→ MCP 网络研究 → 多篇候选稿 → 对抗性评审 → 终稿编辑 → 质量审核/重写 → Redis 落库

也可以直接打开对话页面,在左侧 Agent Tools 中选择"颠覆性改编",粘贴短文后发送。头脑风暴完成后,页面会按综合分展示多个方向及其阅读吸引力、当前网络热度和评分理由;用户选择后,工作流才继续研究、写作与审核。

启动前需要可用的 Redis 和聊天模型。网络研究默认复用现有 Fetch MCP,因此需要安装 uvx;如果请求设置 researchEnabled: false,则跳过网络研究。

创建工作流:

ini 复制代码
$body = @{
  title = "雨夜"
  sourceText = "这里填写需要改编的短文......"
  targetAudience = "成年文学读者"
  constraints = @("保留雨伞意象", "控制在1500字以内")
  researchEnabled = $true
  outputStyle = "LITERARY_PROSE"
} | ConvertTo-Json

$result = Invoke-RestMethod `
  -Method Post `
  -Uri "http://localhost:8898/api/rewrite-workflows" `
  -ContentType "application/json" `
  -Body $body

$result

接口返回 HTTP 202 Accepted 和 workflowId。查询执行状态及最终结果:

bash 复制代码
Invoke-RestMethod "http://localhost:8898/api/rewrite-workflows/$($result.workflowId)"

当状态进入 AWAITING_DIRECTION_SELECTION 后,从返回的 directions 中选择 1 到 maxDirectionSelections 个方向并继续:

perl 复制代码
$selection = @{
  directionIds = @("structure-1", "style-2")
} | ConvertTo-Json

Invoke-RestMethod `
  -Method Post `
  -Uri "http://localhost:8898/api/rewrite-workflows/$($result.workflowId)/directions" `
  -ContentType "application/json" `
  -Body $selection

最终状态包括:

  • AWAITING_DIRECTION_SELECTION:方向评估完成,等待用户提交选择;
  • COMPLETED:终稿达到质量门槛;
  • NEEDS_REVIEW:达到最大重写次数但仍需要人工审核;
  • FAILED:模型、MCP 或 Redis 节点失败;
  • events:全过程节点轨迹;
  • directions:包含 appealScore、networkHeatScore、networkHeatAvailable 和综合 score;
  • directionTrendEvidence、researchEvidence、candidates、reviews、finalDraft:各阶段结构化产物。

outputStyle 支持以下值:

值 页面名称 约束效果
LITERARY_PROSE 文学散文 连续文学段落,禁止档案、公文、报告和剧本格式
PRESERVE_ORIGINAL_GENRE 保持原文体裁 保持分析节点识别出的原始体裁
EXPERIMENTAL 文体实验 允许明显的结构与文体实验
FICTION 小说化改编 使用场景、行动、对话和完整叙事弧线
DRAMA_SCRIPT 戏剧/剧本 使用场景、角色、动作和对白
DOSSIER_EPISTOLARY 档案或书信体 使用档案、报告、书信或日记推进叙事

工作流会把输出风格作为硬约束传递给头脑风暴、方向评估、候选稿、终稿和质量审核。终稿只有在综合分与 genreComplianceScore 均达到质量阈值时才会进入 COMPLETED。

为不同角色配置不同模型

默认所有角色使用 langchain4j.chat.provider 指定的主模型。可以将角色直接映射到 ollama、openai、zhipu 或 aihub:

yaml 复制代码
rewrite-workflow:
  provider-by-role:
    ANALYZER: aihub
    BRAINSTORM_STRUCTURE: openai
    BRAINSTORM_STYLE: openai
    BRAINSTORM_WORLD: ollama
    CREATIVE_DIRECTOR: aihub
    RESEARCH_PLANNER: aihub
    DRAFT_WRITER: openai
    CRITIC: aihub
    FINAL_EDITOR: openai
    QUALITY_REVIEWER: aihub

对应 provider 的地址、模型名和 API Key 仍在 langchain4j.chat 下配置。也可以通过 model-by-role 指向自定义的 ChatModel Spring Bean;model-by-role 的优先级高于 provider-by-role。

接入 MCP 搜索服务

默认 Fetch MCP 只能读取研究策划节点给出的明确 URL。如需真正的搜索能力,可以接入任意 stdio MCP 搜索服务:

yaml 复制代码
rewrite-workflow:
  research:
    search-enabled: true
    search-command:
      - npx
      - -y
      - your-mcp-search-server
    environment:
      SEARCH_API_KEY: ${SEARCH_API_KEY:}
    timeout: 120s

工作流只向 MCP 子进程传递配置的环境变量,不允许模型生成命令。MCP 搜索失败时,如果研究任务包含明确 URL,会自动回退到 Fetch MCP。

方向热度必须来自实时 MCP 搜索证据。未启用搜索、搜索调用失败或用户关闭网络研究时,页面明确显示"无实时数据",networkHeatScore 为 0,综合分仅按阅读吸引力排序,不会让模型臆测网络热度。

轻量 DAG 工作流 MVP

项目包含一套初步通用 DAG 运行内核,支持:

  • START、LLM、MCP_TOOL、CONDITION、END 节点;
  • 使用 $.nodes.<nodeId>.output... 引用前序节点输出;
  • 在边上配置 EQ、NE、数值比较、CONTAINS、EXISTS、TRUTHY、FALSY 条件;
  • ANY 和 ALL 两种入边合并语义;
  • 工作流输入、节点输入和节点输出的基础 JSON Schema 校验;
  • 节点超时、有限重试、运行事件以及 Redis 状态保存;
  • LLM 节点通过 role 复用 rewrite-workflow.model-by-role/provider-by-role 模型路由;
  • MCP 节点当前支持 operation: research,复用现有 MCP 搜索/网页研究能力。

先校验示例定义:

perl 复制代码
$payload = Get-Content -Raw "examples/dag-workflow-run-example.json" | ConvertFrom-Json
$definition = $payload.definition | ConvertTo-Json -Depth 30
Invoke-RestMethod `
  -Method Post `
  -Uri "http://localhost:8898/api/dag-workflows/validate" `
  -ContentType "application/json" `
  -Body $definition

启动运行并查询结果:

perl 复制代码
$body = Get-Content -Raw "examples/dag-workflow-run-example.json"
$run = Invoke-RestMethod `
  -Method Post `
  -Uri "http://localhost:8898/api/dag-workflows/runs" `
  -ContentType "application/json" `
  -Body $body

Invoke-RestMethod "http://localhost:8898/api/dag-workflows/runs/$($run.runId)"

这是执行内核的第一阶段:工作流定义随启动请求提交,运行状态以 Redis 单 Key 保存,调度使用本地线程池。工作流定义版本库、持久消息队列、人工节点、动态 FOREACH/JOIN、MCP Server 注册中心和可视化画布尚未包含在该 MVP 中。

Agent 平台内核

项目包含三个相互独立但可以组合使用的核心组件:

  • Agent Runtime:执行"模型决策、能力调用、观察结果、继续或结束"的多轮循环。
  • Capability Registry:保存 Agent 可使用的能力、模型工具名、输入 Schema 和提供方信息。
  • MCP Invocation Service:管理 MCP 长连接、发现 Tools、校验参数并执行调用。

配置一个 stdio MCP Server:

yaml 复制代码
agent-platform:
  mcp:
    servers:
      web-fetch:
        enabled: true
        transport: STDIO
        command: ["uvx", "mcp-server-fetch"]
        timeout: 120s

也可以配置 Streamable HTTP:

yaml 复制代码
agent-platform:
  mcp:
    servers:
      remote-tools:
        enabled: true
        transport: STREAMABLE_HTTP
        url: https://example.com/mcp
        headers:
          Authorization: ${REMOTE_MCP_AUTHORIZATION:}

服务启动后,先发现并注册 MCP Tools:

bash 复制代码
POST /api/agent-platform/mcp/servers/web-fetch/refresh
GET  /api/agent-platform/capabilities

然后运行 Agent:

bash 复制代码
POST /api/agent-platform/runs
Content-Type: application/json

请求示例见 examples/agent-run-example.json。Agent 只能看到 capabilityIds 明确授权的能力,达到 maxSteps 后会自动停止。DAG 工作流也已支持 AGENT 节点,节点通过 config.systemPrompt、config.capabilityIds 和输入中的 objective 启动同一个 Agent Runtime。

现有功能已统一接入这套内核:

  • 普通聊天直接进入 Agent Runtime,由模型在已授权能力中自主选择,不再先调用模型进行工具分类。
  • GitHub、Fetch 和文章改编研究不再各自创建 MCP Client,统一复用 MCP Connection Manager。
  • tool-security.github、tool-security.fetch 和 rewrite-workflow.research 会自动映射为平台 MCP Server,原配置继续有效。
  • 文件系统只读操作注册为 LOCAL_TOOL Capability;写入和删除不会自动暴露给通用 Agent。
  • 上传文档检索注册为 KNOWLEDGE Capability,并按照本次请求的 fileIds 隔离检索范围。
  • DAG 的 MCP_TOOL 节点可通过 config.capabilityId 调用任意已注册 MCP Tool;原来的 operation: research 仍兼容。

普通聊天入口仍为 POST /api/ai/stream,但其内部已经统一走 Agent Runtime。返回仍采用 SSE,当前 Agent 完成后一次性发送最终答案;逐 Token 与逐 Step 事件流将在后续运行事件层实现。

启用文档问答

文档信息临时保存在 Redis 中,向量由 Ollama 嵌入模型生成。先启动 Redis,再运行项目:

vbscript 复制代码
redis-server

如果 Redis 不在本机,可设置:

ini 复制代码
$env:REDIS_HOST = "redis.example.com"
$env:REDIS_PORT = "6379"
$env:REDIS_PASSWORD = "密码"

当前嵌入模型只支持 Ollama,通过 OLLAMA_EMBEDDING_MODEL 指定模型名称。

项目会优先使用 Docling 将复杂文档转换为 Markdown,再交给 LangChain4j 切片和向量化。Docling 适合 PDF、Word、PPT、Excel、HTML 等结构复杂的文档,能更好保留标题、表格和阅读顺序。

安装 Docling:

css 复制代码
python -m venv .venv-docling
..venv-docling\Scripts\python.exe -m pip install --upgrade pip
..venv-docling\Scripts\python.exe -m pip install docling
..venv-docling\Scripts\docling.exe --help

默认配置会优先调用项目内的 .venv-docling/Scripts/docling.exe。如果你使用全局安装的 Docling,可以通过 DOCLING_COMMAND 覆盖。

如果本机没有安装 Docling,项目会自动回退到原生解析器;原生解析器目前只支持 PDF 和 TXT。也可以显式关闭 Docling:

ini 复制代码
$env:RAG_DOCLING_ENABLED = "false"

工具安全限制统一配置在 tool-security 下。常用环境变量如下:

ini 复制代码
$env:GITHUB_TOOL_ENABLED = "true"
$env:GITHUB_ALLOWED_REPOSITORIES = "langchain4j/langchain4j,melon1010/LangChain4j-Springboot"
$env:FETCH_ALLOWED_DOMAINS = "docs.langchain4j.dev,github.com"
$env:FETCH_MAX_RESPONSE_LENGTH = "200000"
$env:FILESYSTEM_ALLOWED_ROOTS = "D:\safe-directory"
$env:FILESYSTEM_ALLOW_WRITE = "false"
$env:FILESYSTEM_ALLOW_DELETE = "false"
$env:FILESYSTEM_MAX_READ_BYTES = "1048576"
$env:DOCLING_OCR_ENABLED = "true"
$env:DOCLING_OCR_ENGINE = "rapidocr"
$env:DOCLING_OCR_LANG = "ch"

RAG 切片参数可以通过环境变量调整:

ini 复制代码
$env:RAG_SPLITTER_MAX_SEGMENT_SIZE = "800"
$env:RAG_SPLITTER_MAX_OVERLAP_SIZE = "120"
$env:RAG_RETRIEVER_MAX_RESULTS = "6"
$env:RAG_RETRIEVER_MIN_SCORE = "0.55"

启用扩展工具

GitHub 工具:

ini 复制代码
$env:GITHUB_TOKEN = "你的 GitHub Token"
node --version
npx --version

网页抓取工具:

css 复制代码
uvx --version

文件系统工具:

ini 复制代码
$env:FILESYSTEM_ALLOWED_ROOTS = "D:\safe-directory"
$env:FILESYSTEM_ALLOW_WRITE = "false"
$env:FILESYSTEM_ALLOW_DELETE = "false"

多个允许目录使用英文逗号分隔。只有明确需要时才应开启写入或删除权限。

构建可执行包

bash 复制代码
mvn clean package
java -jar target/openmanus-springboot-0.0.1-SNAPSHOT.jar

常见问题

  • 页面能打开但模型无回复:确认所选模型服务已启动、API Key 有效,且 base-url 使用 OpenAI 兼容地址而不是 Anthropic 专用地址。
  • 文档上传失败:确认 Redis 已启动;超过 Spring 上传限制的文件会被拒绝。
  • 文档无法回答:确认 Ollama 已启动并已下载嵌入模型;复杂文档建议安装 Docling,否则只能回退解析 PDF 和 TXT。
  • GitHub 查询失败:确认 Node.js、npx 和 GITHUB_TOKEN 可用。
  • 网页抓取失败:确认已安装 Python 环境和 uvx,并且目标网站可访问。
  • 端口冲突:通过 SERVER_PORT 覆盖 Spring Boot 默认端口,例如 $env:SERVER_PORT = "8081"。

源码

github.com/Vecter8357/...

相关推荐
张一凡931 小时前
把“不要乱改”从提示词里拿出来,放进可校验的提案里
前端·后端·架构
一只爱撸猫的程序猿1 小时前
当任何人都能生成一万行代码:什么变得稀缺
人工智能·后端·程序员
Potato_土豆1 小时前
MCP Server 接入多个 AI 客户端:配置差异与 10 个真实踩坑
人工智能·后端·ai编程
低调滴开发1 小时前
3 行代码写崩一个函数?切片才是 Rust 字符串处理的正确姿势
开发语言·后端·rust
代码丰1 小时前
跨系统传输大 JSON:直接压缩,还是把数据放进 Redis?
redis·后端·json
136096757231 小时前
RAG 第一步就错了:切分粒度决定召回上限
后端
LEE2 小时前
别把 Agent 架构画成组织架构图——子代理该不该拆,用任务级 Eval 说话
前端·后端·ai编程
合尘猫2 小时前
词元涨价近一倍后,我把 AI 客服的成本拆成了四个可优化的变量
人工智能·后端·架构
AngusTester2 小时前
自建 Git 之后怎么推代码:配 SSH 或 HTTPS,完成第一次 push
后端