☰
agent-core-java 中 InferenceAffinityModelClient 的实现解析:KV Cache 共享、释放与 vLLM 会话亲和调用
2026/10/13 9:25:30 网站建设 项目流程
  • 人工智能
  • AI Agent
  • Agent 框架
  • 工具调用
  • Agent 工作流
  • RAG
  • MCP Clients
  • 强化学习

【免费下载链接】agent-core-java

openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。

项目地址:https://gitcode.com/openJiuwen/agent-core-java
点击查看免费下载

本文围绕 agent-core-java 仓库中的 InferenceAffinityModelClient 展开:这是一个面向 InferenceAffinity 风格(vLLM)推理服务的模型客户端,在标准 OpenAI 兼容/v1/chat/completions调用之上,额外提供cache_sharing(基于session_id的会话级 KV Cache 共享)与release(主动释放已缓存上下文)两项能力。读完本文,你能掌握该客户端的配置方式、请求参数组装链路、/release_kv_cache释放协议,以及它与InferenceAffinityModel、KVCacheManager之间从源码到测试的完整调用关系。

客户端定位:InferenceAffinity 风格的会话级缓存客户端

InferenceAffinityModelClient继承自 BaseModelClient,位于com.openjiuwen.core.foundation.llm.modelclients包中,其源码 Javadoc 明确说明它“mirrors Python'sInferenceAffinityModelClient”(参见 InferenceAffinityModelClient.java)。与仓库中其他 OpenAI 兼容客户端相比,它的差异化能力集中在三点:

  • 会话级 KV Cache 共享:在请求体中附带cache_sharing: true与cache_salt: <sessionId>,让服务端按会话维度复用 KV Cache;
  • 缓存主动释放:通过私有端点POST /release_kv_cache释放指定会话、指定窗口之后的缓存;
  • 能力自声明:覆写supportsKvCacheRelease()返回true(源码 L303-L306),使上层的 KV Cache 管理组件知道可以对该客户端执行释放操作。

从源码结构看,它只实现了两类请求路径,均写入两个类级常量(L53-L54):

常量路径用途
CHAT_COMPLETIONS_PATH/v1/chat/completions同步invoke与流式stream的补全端点
RELEASE_KV_CACHE_PATH/release_kv_cache释放会话已缓存上下文的私有端点

图像、语音、视频生成(generateImage/generateSpeech/generateVideo)在该客户端中均直接返回null(L206-L242),即该 provider 面向的是纯文本对话补全场景,使用时无需考虑多模态生成路径。

构造方法、注册机制与两层配置

两个构造入口

客户端提供两个构造入口(L63-L74):

public InferenceAffinityModelClient(ModelRequestConfig modelConfig, ModelClientConfig modelClientConfig) public InferenceAffinityModelClient(Map<String, Object> kwargs) // 从 kwargs 取 model_config / model_client_config

主构造器先调用super(modelConfig, modelClientConfig)完成基类校验,再通过createHttpClient(modelClientConfig)基于配置构建 JDKHttpClient(L523-L527)。第二个kwargs构造器是供ClientRegistry反射实例化使用的入口,它从model_config与model_client_config两个键中取出配置对象后委托给主构造器。

此外,客户端在静态块中调用registerClientClass(InferenceAffinityModelClient.class)完成自注册(L59-L61),同时存在一个显式工厂 InferenceAffinityModelClientFactory,其create(...)方法等价于直接new InferenceAffinityModelClient(modelConfig, clientConfig)(见 工厂源码)。

provider 路由:InferenceAffinity是内置 provider

ProviderType枚举中定义了INFERENCE_AFFINITY("InferenceAffinity", "InferenceAffinity")(ProviderType.java L24)。在 ModelClients 的内置映射表中,InferenceAffinity被直接绑定到InferenceAffinityModelClient类名(L62)。因此通过ModelClients.createModelClient(clientConfig, modelConfig)创建客户端时,只要client_provider填写InferenceAffinity,就会走内置反射路径实例化该类,无需手动registerFactory。这一行为与 接入大模型 文档中列出的内置 provider 名单一致。

连接层配置:ModelClientConfig 关键参数

构造客户端所需的连接配置来自 ModelClientConfig,常用字段及其在本客户端中的实际作用如下:

字段默认值在本客户端中的作用
clientProvider无必须为InferenceAffinity(大小写兼容匹配,建议按内置名称原样填写)
apiKey无必填。基类validateConfig()会校验api_key非空,否则抛出MODEL_SERVICE_CONFIG_ERROR(BaseModelClient.java L146-L160)
apiBase无必填。推理服务根地址,客户端会自动去掉末尾/后拼接路径(trimTrailingSlash,L650-L659)
timeout60.0请求超时(秒),必须大于 0;单次调用可通过invoke的timeout参数覆盖
maxRetries3同步invoke的重试次数上限(语义见下文)
verifySsltrue是否校验证书,自签证书环境可临时关闭
httpVersion无指定HttpClient版本,如ModelHttpVersion.HTTP_1_1

一个最小可用配置示例(形态与测试用例 InferenceAffinityModelClientTest L232-L242 一致):

ModelRequestConfig requestConfig = ModelRequestConfig.builder() .modelName("qwen2-72b") .build(); ModelClientConfig clientConfig = ModelClientConfig.builder() .clientProvider(ProviderType.INFERENCE_AFFINITY) // 即 "InferenceAffinity" .apiKey("sk-test") .apiBase("http://localhost:8000") .verifySsl(false) .httpVersion(ModelHttpVersion.HTTP_1_1) .build(); InferenceAffinityModelClient client = new InferenceAffinityModelClient(requestConfig, clientConfig);

同步 invoke:kwargs 剥离、cache_sharing 注入与响应解析

请求参数组装链路

invoke(messages, tools, temperature, topP, model, maxTokens, stop, outputParser, timeout, kwargs)的执行步骤如下(L115-L158):

  1. kwargs 副本与内部键剥离:复制kwargs后移除tracer_record_data、session_id、enable_cache_sharing三个内部键,确保它们不会混入最终 HTTP 请求体——测试用例invokePostsChatCompletionAndParsesUsageToolsAndParserContent明确断言了lastPayload不包含这三个键(测试 L138)。
  2. 参数组装:buildAndSanitizeParams(...)先调用基类buildRequestParams组装标准 OpenAI 兼容参数(model、messages、temperature、top_p、max_tokens、stream: false等),随后执行两件事(L81-L112):
    • sanitizeToolCalls(messages):清洗历史 assistant 消息中的tool_calls,只保留id、type: "function"、index与function.name/arguments字段;
    • 当enableCacheSharing == true且sessionId非空时,注入cache_sharing: true与cache_salt: sessionId。
  3. 发送与重试:makeAsyncRequest(params, timeout)按maxRetries循环发送,每次失败后按2^attempt秒退避(L308-L329)。注意其语义与 OpenAI 客户端略有差异:这里maxRetries直接作为最大尝试总次数使用(for attempt < maxRetries),配置为 0 时一次都不发。
  4. 响应解析:parseResponse从choices[0].message中解析content、reasoning_content、tool_calls与usage,并对 content 执行outputParser.parse(...)得到parserContent(L331-L353)。

响应字段映射

解析结果封装为AssistantMessage,字段映射关系(均有测试断言佐证,见 测试 L139-L150):

响应字段映射目标说明
choices[0].message.contentcontent正文内容,缺失时取空串
choices[0].message.reasoning_contentreasoningContent推理链内容(如存在)
choices[0].message.tool_callstoolCalls解析为ToolCall列表;非空时finishReason置为tool_calls,否则为stop
usage.prompt_tokens / completion_tokens / total_tokensusageMetadata输入 / 输出 / 总 token
usage.prompt_tokens_details.cached_tokensusageMetadata.cacheTokens仅同步invoke路径会读取(includeCacheTokens=true),用于观察 KV Cache 命中

cacheTokens正是 cache_sharing 是否生效的直接观测点:测试中prompt_tokens_details.cached_tokens: 5被断言映射到usageMetadata.getCacheTokens()(测试 L143)。

错误处理

调用链中所有异常最终收敛为StatusCode.MODEL_CALL_FAILED的BaseError,错误信息分别为InferenceAffinity API async invoke error: ...(同步)与InferenceAffinity API async stream error: ...(流式),便于在日志中按 provider 定位问题。

流式 stream:SSE 行解析与增量 parser

stream(...)与invoke的差别仅在于stream: true与 SSE 解析(L160-L204)。其内部流程:

  1. postStream复用postJson拿到响应体后按行切分(L461-L465);
  2. parseStreamChunk(line)只处理data:前缀行,忽略data: [DONE]结束标记;对每条 chunk 解析choices[0].delta中的content、reasoning_content、tool_calls,以及顶层usage与finish_reason(L414-L453)。单条 chunk 解析失败(JSON 异常、字段缺失)不会中断流,只记录 warning 并跳过;
  3. 若传入了outputParser,则走streamWithParser:累积content,对累积串尝试增量解析,解析成功后把结果挂到该 chunk 的parserContent并清空累积器(L355-L392)。

测试streamParsesSseChunksAndAppliesIncrementalParser用三条 SSE 行验证了该行为(测试 L154-L188):

data: {"choices":[{"delta":{"content":"Hello"}}]} data: {"choices":[{"delta":{"content":" world"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":2,"total_tokens":3}} data: [DONE]

断言点包括:请求体携带model: override-model、max_tokens: 32、stream: true;产出 2 个 chunk,第 2 个 chunk 的finishReason为stop且usageMetadata.totalTokens为 3。

release:KV Cache 释放协议

release(sessionId, messages, messagesReleasedIndex, tools, toolsReleasedIndex, model)是该客户端最具特色的方法(L244-L301),其协议细节:

  • 请求体(POST /release_kv_cache,无调用侧 timeout 参数):
字段来源说明
model入参model,缺省回退modelConfig.getModelName()目标模型名
cache_saltsessionId与会话绑定的缓存标识,和请求时的cache_salt一致
cache_sharing恒为true标记这是共享缓存的释放请求
messages经convertMessagesToDict+sanitizeToolCalls清洗后的消息列表服务端据此定位缓存窗口
messages_released_index入参从该索引(含)开始的消息对应的缓存被释放
tools/tools_released_index可选仅在非空时放入请求体
  • 返回语义:HTTP 200 返回true并记录KV cache release successful日志;非 200 记录 error 日志并返回false(不抛异常);其他异常包装为MODEL_CALL_FAILED(错误前缀Release error: ...)。

测试releasePostsKvCachePayloadAndReturnsFalseForNonSuccessStatus验证了完整请求体(model、cache_salt、cache_sharing、messages_released_index: 1、tools_released_index: 0)以及 500 状态码时返回false的行为(测试 L190-L228)。

与上层 KVCacheManager 的协作

从源码结构看,release的消费方是上下文层的 KV Cache 管理组件:KVCacheManager 定义了ReleaseCapableModel接口(L144-L147),而 InferenceAffinityModel 实现了该接口并在内部委托给InferenceAffinityModelClient.release(...)。当上下文窗口发生不可逆变更(如压缩、截断)时,KVCacheManager通过firstChangedDecision计算出首个变更索引(ReleaseDecision),若判定需要释放,就对实现了ReleaseCapableModel的模型发起release(sessionId, messages, messagesReleasedIndex, tools, toolsReleasedIndex)(invokeRelease 实现)。对应的 API 文档可参考 KVCacheManager 与 InferenceAffinityModel。

InferenceAffinityModel:面向上层的统一调用入口

除直接使用 client 外,推荐的调用入口是 InferenceAffinityModel。它把session_id与enable_cache_sharing提升为一等参数:

public CompletionStage<AssistantMessage> invoke(Object messages, List<?> tools, Float temperature, Float topP, Integer maxTokens, String stop, String model, BaseOutputParser outputParser, String sessionId, boolean enableCacheSharing, Map<String, Object> kwargs) public Iterator<AssistantMessageChunk> stream(Object messages, /* 同上参数 */ ...) public CompletionStage<Boolean> release(String sessionId, List<?> messages, Integer messagesReleasedIndex, List<?> tools, Integer toolsReleasedIndex, String model)

其内部normalizeKwargs会把sessionId/enableCacheSharing写回 kwargs 后透传给 client(L171-L179),与上文 client 侧“kwargs 剥离”的逻辑正好配对。静态辅助方法buildKvCacheInvokeKwargs(session, enableKvCacheRelease)则用于从 session 对象中反射读取getSessionId/get_session_id并构造 kwargs(L159-L169)。

需要注意的是,InferenceAffinityModel创建底层 client 时走的是反射适配层ReflectiveInferenceAffinityClient(按方法名 + 参数个数绑定invoke/stream/release,L274-L382),从源码结构看,这一设计保持了 Python 版Model/ModelClient两件套的依赖解耦形态。

使用边界与注意事项

结合仓库文档与源码,使用该客户端时有以下前提和边界:

  1. 服务端必须实现私有端点:/release_kv_cache不属于 OpenAI 协议,仅 InferenceAffinity 风格(vLLM 扩展)网关提供;对普通 OpenAI 端点发起释放请求没有意义。cache_sharing/cache_salt字段同样依赖服务端支持。
  2. 与 AscendAffinity 亲和模式互斥:仓库文档 Ascend算力亲和 明确指出,Ascend-affinity(通过OpenAIModelClient+agent_hint实现 prefetch/offload/evict)与 InferenceAffinity 的 release 模式互斥,二者不可同时启用。选型时可参考 examples/kv-cache-demo 中的 Ascend-affinity 调度演示。
  3. 重试语义:makeAsyncRequest中maxRetries是总尝试次数上限(默认 3),失败退避为2^attempt秒,与 接入大模型 文档中 OpenAI 客户端“追加重试次数”的表述不同,调参时需注意。
  4. 超时覆盖:invoke/stream的timeout参数(秒)会覆盖ModelClientConfig.timeout;内部换算为Duration时不足 1ms 会向上取整到 1ms(timeoutDuration)。
  5. HTTP 版本:HttpClient由ModelHttpClients按httpVersion构建,测试用例验证了HTTP_1_1可正确生效(测试 L231-L243)。

小结

InferenceAffinityModelClient在 agent-core-java 中承担着一个明确的职责:在 OpenAI 兼容对话协议之上,把“会话级 KV Cache 共享”与“缓存窗口主动释放”落成两个可复现的 HTTP 行为——请求时以cache_salt绑定会话、读取cached_tokens观察命中;窗口不可逆变化时经InferenceAffinityModel.release调用/release_kv_cache回收旧缓存。它的实现细节(kwargs 剥离、tool_calls 清洗、SSE 增量解析、指数退避重试、200/非 200 的差异化返回)均可在 InferenceAffinityModelClient.java 与 InferenceAffinityModelClientTest.java 中逐条对照验证,适合作为理解该仓库“客户端—模型—KV Cache 管理”三层协作的切入点。

  • 人工智能
  • AI Agent
  • Agent 框架
  • 工具调用
  • Agent 工作流
  • RAG
  • MCP Clients
  • 强化学习

【免费下载链接】agent-core-java

openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。

项目地址:https://gitcode.com/openJiuwen/agent-core-java
点击查看免费下载

相关推荐

上一篇:TableExport终极指南:3分钟实现专业级HTML表格数据导出方案
下一篇:Shenyu生物识别:安全认证新维度

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询