Agentx-Kotlin核心设计
更新: 3/27/2026 字数: 0 字 时长: 0 分钟
大模型服务供应商
提供大模型的公司,比如火山引擎下属的豆包,同义千问下的qwen-coder
有时大模型提供商会有一个modleId(模型名称)和一个eployId(部署ID),这种情况常见于不生产模型只提供服务的服务商(比如硅基流动)
大模型协议
由于不同的服务商对自己的API有着不同的规矩,因此会推出不同的协议,简单来说其实就是一个Http的请求格式
举个例子,OpenAI的协议就是
由于不同的服务商对自己的API有着不同的规矩,因此会推出不同的协议,简单来说其实就是一个Http的请求格式
举个例子,OpenAI的协议就是
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "system",
"content": "你是一个得力的助手。"
},
{
"role": "user",
"content": "你好,请简要解释什么是 API 协议。"
}
],
"temperature": 0.7
}'目前OpenAI协议是最常用的协议
SSE
当前的大模型对话返回的结果都是流式输出的模式,所谓流式输出,我们随意打开一个大模型界面进行一轮问话,我们很自然的就能发现他的输出是一个字一个字的向外输出,这就是SSE的效果
我们打开F12模式,可以看到有一个网络请求,其Content-Type是text/event-stream类型,并且具有一个EventStream,里面是一次次流式输出的内容
| 特性 | HTTP 轮询 (Polling) | SSE (Server-Sent Events) | WebSocket |
|---|---|---|---|
| 通讯模式 | 单向 (客户端请求) | 单向 (服务器推送) | 全双工 (双向通讯) |
| 协议 | 标准 HTTP | 标准 HTTP | 基于 TCP 的独立协议 (ws/wss) |
| 连接状态 | 频繁建立/断开连接 | 长连接 | 长连接 |
| 数据格式 | 任意 (通常 JSON) | 仅限文本 (UTF-8) | 文本、二进制 |
| 重连机制 | 需客户端手动重发 | 内置自动重连 | 需手动实现重连逻辑 |
| 浏览器兼容性 | 极好 | 较好 (IE/Edge 需 Polyfill) | 极好 |
这里简单的列举了一下SSE,Http轮询和WebSocket的区别,其实不难发现,SSE就是刚好符合我们目前的需求的,即客户端向服务端发起一次请求,然后服务端给客户端多次回复
Token
大模型存在上下文窗口的限制,对话在大模型中以Token的方式进行计数,当Token超出了大模型的最大限制,那么大模型就会出现失忆的情况,因此我们会希望尽可能的希望大模型的记忆中存储有效的信息
三种Token处理策略
- 无策略:不对对话进行任何处理,直接发送所有信息给到LLM,可能会导致Token超限报错
- 滑动窗口策略: 基于Token策略保存最新的消息,超出窗口的消息直接丢弃 缺点:历史消息会被完全丢失,会影响对话的连续性 需要的参数:
最大Token数:用于计算窗口的大小,必须项,通常用来对LLM的上下文进行限制预留缓存比例:用于留给新对话的大小,可以设置一个默认值,如10% - 摘要策略: 将超出阈值的早期消息生成摘要,保留摘要和最新消息
缺点:需要额外调用LLM生成摘要,有延迟和成本
需要的参数:摘要触发阈值:触发摘要的消息数量,默认20条最大Token数:总Token上限,默认为所用LLM的上下文限制
工厂模式实现策略的加载
在AgentX中,我们使用了工厂模式对Token的使用策略进行了计算,所有的代码通过TokenDomainService进行计算,再统一通过TokenOverflowStrategyFactory根据配置的策略进行选择
@Service
class TokenDomainService(
private val strategyFactory: TokenOverflowStrategyFactory
) {
fun processMessages(messages: List<TokenMessage>, config: TokenOverflowConfig): TokenProcessResult =
strategyFactory.createStrategy(config).process(messages, config)
}
@Service
class TokenOverflowStrategyFactory {
fun createStrategy(config: TokenOverflowConfig?): TokenOverflowStrategy =
config?.let { createStrategy(it.strategyType, it) } ?: NoTokenOverflowStrategy()
fun createStrategy(strategyType: TokenOverflowStrategyEnum?, config: TokenOverflowConfig): TokenOverflowStrategy =
when (strategyType ?: TokenOverflowStrategyEnum.NONE) {
TokenOverflowStrategyEnum.SLIDING_WINDOW -> SlidingWindowTokenOverflowStrategy(config)
TokenOverflowStrategyEnum.SUMMARIZE -> SummarizeTokenOverflowStrategy(config)
TokenOverflowStrategyEnum.NONE -> NoTokenOverflowStrategy(config)
}
fun createStrategy(strategyName: String?, config: TokenOverflowConfig): TokenOverflowStrategy =
createStrategy(TokenOverflowStrategyEnum.fromString(strategyName), config)
}每一次chat都会经过TokenDomainService中的process方法,然后将计算的结果保存到Context里面
Context和Message的区别
为了更好的用户体验,我们会在对话到达一定的量的时候将指定条数的内容传递给大模型,并让其生成总结,这个总结的内容是不可以存放到message里面的,因为message表中的内容是要给用户看的,因此我们会另开一张表专门存放给Agent用来作为上下文的消息,这个表就是Context表