“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。
md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。
要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。
在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。
要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。
工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。
写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。
适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。
你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。
全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。
这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。
不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。
产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。
如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。
用 ChatModel 构建 LLM 驱动的 Java 应用
最近在搞 LLM 应用开发,发现 Spring AI 里有个 ChatModel 挺好用的,分享一下我的实践笔记。
为啥用 ChatModel
说白了,ChatModel 是 Spring AI 里跟大模型对话的核心接口。不管你是用 OpenAI、通义千问还是本地跑个 Llama,只要接上 ChatModel,代码基本不用怎么改。它主要帮你干几件事:
- 统一调用方式,不用记各家 SDK 的奇奇怪怪 API
- 内置对话管理,可以自己控制上下文
- 支持流式输出,不用等 LLM 慢吞吞吐完一整段
- 还带一些工具调用、函数调用的能力,挺灵活
快速上手
1. 加依赖
我用的是 Maven,在 pom.xml 里加上 Spring AI 的 BOM 和具体的模型实现:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-M6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
</dependencies>
如果你用通义千问或者别的,换对应的 starter 就行,比如 spring-ai-qwen-spring-boot-starter。
2. 配置 API Key
在 application.yml 里配一下(别把 key 硬编码到代码里,我吃过亏):
spring:
ai:
openai:
api-key: sk-xxxxxxxxxxxxxxxx
chat:
model: gpt-4o-mini
options:
temperature: 0.7
max-tokens: 1024
3. 写个最简单的对话
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/chat")
public class ChatController {
@Autowired
private ChatModel chatModel;
@PostMapping("/simple")
public String simpleChat(@RequestBody String message) {
Prompt prompt = new Prompt(message);
return chatModel.call(prompt).getResult().getOutput().getContent();
}
}
跑起来,POST 一个 {"message": "你好,介绍一下 Spring AI"},它就会返回 LLM 的回答。就这么简单。
进阶用法
带上下文的对话
单次调用没啥意思,实际场景里通常要记住上下文。可以用 ChatMemory 来搞:
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ChatService {
@Autowired
private ChatModel chatModel;
private final ChatMemory chatMemory = new InMemoryChatMemory();
public String chat(String sessionId, String userMessage) {
// 获取历史消息
List<Message> history = chatMemory.get(sessionId, 10);
// 加上用户新消息
history.add(new UserMessage(userMessage));
// 调用模型
Prompt prompt = new Prompt(history);
String response = chatModel.call(prompt).getResult().getOutput().getContent();
// 把回答存进去
history.add(new AssistantMessage(response));
chatMemory.put(sessionId, history);
return response;
}
}
InMemoryChatMemory 是内存实现的,重启就没了。生产环境可以用 Redis 或者数据库,实现一个 ChatMemory 接口就行。
流式输出
LLM 回答慢的时候用户容易焦虑,流式输出体验好很多:
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/chat")
public class StreamingChatController {
@Autowired
private ChatModel chatModel;
@PostMapping(value = "/stream", produces = "text/event-stream")
public Flux<String> streamChat(@RequestBody String message) {
Prompt prompt = new Prompt(message);
return chatModel.stream(prompt)
.map(chunk -> chunk.getResult().getOutput().getContent());
}
}
前端用 EventSource 或者 fetch + ReadableStream 就能逐字显示,体验感拉满。
工具调用(Function Calling)
这个才是真正实用的功能——让 LLM 能调用你的 Java 方法。比如让 AI 帮你查天气:
先定义一个工具类:
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
@Component
public class WeatherTools {
@Tool(name = "get_weather", description = "获取指定城市的天气")
public String getWeather(String city) {
// 这里假装调了天气API
if ("北京".equals(city)) {
return "北京今天晴,25°C,空气质量良";
} else if ("上海".equals(city)) {
return "上海今天多云,28°C,湿度较大";
}
return "暂不支持该城市";
}
}
然后在调用时注册工具:
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/chat")
public class ToolChatController {
@Autowired
private ChatModel chatModel;
@Autowired
private List<ToolCallback> toolCallbacks; // Spring 会自动收集 @Tool 注解的方法
@PostMapping("/with-tools")
public String chatWithTools(@RequestBody String message) {
Prompt prompt = new Prompt(message);
// 把工具传给模型
var response = chatModel.call(prompt, toolCallbacks);
return response.getResult().getOutput().getContent();
}
}
这样用户说“北京天气怎么样”,LLM 就会自动调用 getWeather("北京"),拿到结果再组织成自然语言回答。
踩坑记录
- Token 限制:默认 max-tokens 可能不够,长对话记得调大,否则回答会被截断。
- 上下文窗口:
ChatMemory的get(sessionId, 10)里的 10 表示最近 10 条消息,设太大容易超 token 限制,设太小记不住上下文。建议根据模型的最大上下文动态计算。 - 工具注册:
@Tool注解的方法必须是public的,且参数类型要简单(String、int 这些),复杂对象 LLM 不一定能正确生成参数。 - 流式 + 工具调用:目前 Spring AI 的流式工具调用支持还不完善,如果既要流式又要调工具,建议先非流式调工具拿到结果,再流式输出最终回答。
总结
ChatModel 这套 API 设计得挺清爽,把 LLM 调用抽象成了类似 JDBC 的感觉——换模型就换驱动(依赖)和配置,业务代码几乎不用动。对于快速搭建 LLM 应用原型来说,非常顺手。
当然,生产环境还要考虑错误重试、限流、日志追踪这些,Spring AI 也提供了对应的抽象,下次有空再写一篇。