- 人工智能
- AI Agent
- Agent 框架
- 工具调用
- Agent 工作流
- RAG
- MCP Clients
- 强化学习
【免费下载链接】agent-core-java
openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。
InferenceAffinityModel是 openJiuwen agent-core-java(com.openjiuwen.core.foundation.llm包)中专用于InferenceAffinity(vLLM)推理服务的统一会话调用入口。它的核心职责是把sessionId与缓存共享开关(enableCacheSharing)安全、一致地透传给底层InferenceAffinityModelClient,从而让多轮对话的 KV Cache 能在服务端按会话复用,并支持主动释放已缓存的上下文。读完本文,你将掌握:该类的字段与构造约束、invoke/stream/release三个方法族的行为与参数语义、session 与缓存参数的归一化规则,以及它与KVCacheManager、模型客户端工厂在源码层面的协作机制,并可直接照抄文中的配置与调用示例接入真实项目。
类定位:统一会话入口,而不是又一个 HTTP 客户端
InferenceAffinityModel位于 core/src/main/java/com/openjiuwen/core/foundation/llm/InferenceAffinityModel.java,类声明如下:
public class InferenceAffinityModel implements KVCacheManager.ReleaseCapableModel从源码注释可以确认,该类对应 Python 版openjiuwen/core/foundation/llm/inference_affinity_model.py中的同名实现,是 Java 侧的对齐移植。它本身不直接实现 HTTP 协议,而是扮演两个角色:
- 统一入口:把
messages、tools、采样参数以及sessionId、enableCacheSharing打包后透传给实际的模型客户端; - KV Cache 释放能力的载体:实现
KVCacheManager.ReleaseCapableModel接口,使上下文引擎能够在窗口变化时回调release(...)释放旧缓存。
API 文档中对该类的定位描述是"统一 InferenceAffinity 会话调用入口,向InferenceAffinityModelClient透传 session 与缓存共享参数",这与源码实现完全一致。要理解它,建议先区分三张"脸":
| 组件 | 文件 | 职责 |
|---|---|---|
InferenceAffinityModel | InferenceAffinityModel.java | 面向业务代码的统一入口,做参数归一化与校验 |
InferenceAffinityModelClient | modelclients/InferenceAffinityModelClient.java | 真正发 HTTP 请求的客户端,实现 cache sharing 与 release |
InferenceAffinityModelClientFactory | modelclients/InferenceAffinityModelClientFactory.java | 按 provider 名称创建客户端实例的工厂 |
核心字段与构造方法
三个字段
文档列出的私有字段与源码一一对应:
| 字段 | 类型 | 说明 |
|---|---|---|
modelConfig | ModelRequestConfig | 本次请求的模型配置(模型名、采样参数默认值等) |
modelClientConfig | ModelClientConfig | 连接层配置(provider、apiBase、apiKey、超时、重试等) |
client | InferenceAffinityModelClient(内部以InferenceAffinityClient接口持有) | 当前 provider 对应的客户端实例 |
注意文档表格中字段类型写为InferenceAffinityModelClient,源码中实际以窄接口InferenceAffinityModel.InferenceAffinityClient持有(见下文"反射适配"小节),但语义不变:它就是承载真实调用逻辑的客户端对象。
构造方法
public InferenceAffinityModel(ModelClientConfig modelClientConfig, ModelRequestConfig modelConfig)构造时会把两份配置分别保存,并通过createDefaultClient(modelConfig, modelClientConfig)创建默认客户端。源码中对校验逻辑有明确约束(构造器会先做非空校验):
modelClientConfig == null时抛出MODEL_SERVICE_CONFIG_ERROR,错误信息为model client config is none;- 客户端创建失败时抛出
MODEL_SERVICE_CONFIG_ERROR,错误信息为InferenceAffinityModelClient translation is not available或failed to create InferenceAffinityModelClient: ...。
也就是说,连接配置缺失或客户端不可用时,构造阶段就会直接失败,而不是等到首次调用才报错。测试 InferenceAffinityModelTest.java 中constructorRejectsMissingClientConfigLikePython用例验证了这一行为:传入null的ModelClientConfig会抛出包含model client config is none的BaseError。
两层配置从哪来
两个配置类分别解决"怎么连"与"怎么调":
ModelClientConfig(schema/ModelClientConfig.java)描述连接行为,常用字段包括clientProvider(provider 名,支持ProviderType枚举或字符串)、apiBase、apiKey、timeout(默认60.0秒,必须大于 0)、maxRetries(默认3)、verifySsl(默认true)、sslCert、headers/customHeaders、clientId(默认自动生成 UUID)。ModelRequestConfig(schema/ModelRequestConfig.java)描述单次请求参数,包括modelName(默认空字符串,接入时务必显式设置)、temperature(默认0.95)、topP(默认0.1)、maxTokens、stop、user、seed、extraFields。
关于 provider 名称:ProviderType.INFERENCE_AFFINITY("InferenceAffinity", "InferenceAffinity")是内置 provider,工厂注册同时覆盖InferenceAffinity与inference_affinity两个名称(见 modelclients/DefaultModelClientFactories.java),在 接入大模型.md 中也被列入开箱即用的内置 provider 列表。
方法详解
配置访问器
public ModelRequestConfig getModelConfig() public ModelClientConfig getModelClientConfig()返回构造时保存的两份配置对象,便于上层读取模型名、采样参数与连接信息。
invoke:发起同步模型调用
API 文档给出的签名(文档为表述简化的throws Exception风格)在实际源码中是响应式风格:
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)参数语义如下:
| 参数 | 说明 |
|---|---|
messages | 模型输入,支持String、List<BaseMessage>、List<Map<String, Object>>等形态 |
tools | 工具定义列表(List<ToolInfo>或List<Map<String, Object>>) |
temperature/topP/maxTokens/stop/model | 单次调用覆盖默认配置的采样参数;model为空时回退到ModelRequestConfig.modelName |
outputParser | 输出解析器,解析结果写入返回的AssistantMessage.parserContent |
sessionId | 会话标识,用于 KV Cache 复用,透传到底层请求 |
enableCacheSharing | 缓存共享开关,开启后请求会携带cache_sharing与cache_salt |
kwargs | 额外请求参数,会与 session 参数合并后一并透传 |
该方法内部直接委托client.invoke(...),并在透传前调用normalizeKwargs把 session 参数并入 kwargs:
private static Map<String, Object> normalizeKwargs(String sessionId, boolean enableCacheSharing, Map<String, Object> kwargs) { Map<String, Object> options = kwargs == null ? new LinkedHashMap<>() : new LinkedHashMap<>(kwargs); if (sessionId != null) { options.put("session_id", sessionId); } options.put("enable_cache_sharing", enableCacheSharing); return options; }也就是说,即使调用方不传 kwargs,session_id与enable_cache_sharing也一定会被写入透传参数——这正是文档所说"透传 session 与缓存共享参数"的落地实现。测试invokeDelegatesAllOptionsAndCacheKwargsToClient验证了这一点:调用方传入的kwargs(如tracer_record_data)与自动注入的session_id、enable_cache_sharing会同时出现在透传给客户端的参数中。
stream:发起流式模型调用
public Iterator<AssistantMessageChunk> stream(Object messages, List<?> tools, Float temperature, Float topP, Integer maxTokens, String stop, String model, BaseOutputParser outputParser, String sessionId, boolean enableCacheSharing, Map<String, Object> kwargs)与invoke参数完全一致,返回同步Iterator<AssistantMessageChunk>,调用方逐块next()消费即可。每个 chunk 可包含content(文本增量)、reasoningContent、toolCalls、usageMetadata、finishReason、parserContent字段;流式场景下finishReason中间过程通常为"null",结束时才变为stop/tool_calls。底层同样经过normalizeKwargs归一化。
此外invoke与stream各提供一个仅传messages的便捷重载,其余参数全部置null/false,适合快速单轮调用。
release:释放会话与已缓存上下文
public CompletionStage<Boolean> release(String sessionId, List<?> messages, Integer messagesReleasedIndex, List<?> tools, Integer toolsReleasedIndex, String model)messagesReleasedIndex为空时按0处理(releasedIndex = messagesReleasedIndex == null ? 0 : messagesReleasedIndex);- 真正释放逻辑委托给客户端的
release(...),请求会发往服务端的/release_kv_cache端点; - 返回
CompletionStage<Boolean>,HTTP 200 返回true,其余状态返回false。
release同时是KVCacheManager.ReleaseCapableModel接口的实现(该接口签名为release(String sessionId, List<BaseMessage> messages, Integer messagesReleasedIndex, List<ToolInfo> tools, Integer toolsReleasedIndex)),因此它可以被上下文引擎在窗口变化时回调,实现"差异释放"。
静态 KV Cache 辅助方法
public static boolean supportsKvCacheRelease() // 恒为 true public static Map<String, Object> buildKvCacheInvokeKwargs(Object session, boolean enableKvCacheRelease)buildKvCacheInvokeKwargs会从 session 对象解析会话 ID:优先反射调用getSessionId(),找不到再尝试 Python 风格的get_session_id(),两者都不存在则跳过session_id。开启缓存释放时还会写入enable_cache_sharing=true。这是给KVCacheManager等上层组件拼装调用参数用的工具方法。
底层原理:反射适配与客户端 HTTP 协议
为什么要反射?
InferenceAffinityModel通过CLIENT_CLASS_NAME = "com.openjiuwen.core.foundation.llm.modelclients.InferenceAffinityModelClient"在运行时用Class.forName(...)加载客户端,并包装成内部类ReflectiveInferenceAffinityClient。这样做的意义在于:InferenceAffinityModel不硬依赖具体客户端类,客户端实现缺失时只在构造阶段抛出可读的错误信息,而不是在类加载阶段就失败。反射适配层按参数个数查找invoke(10 参)、stream(10 参)、release(6 参)方法,并把返回结果规整为CompletionStage。
InferenceAffinityClient窄接口定义了三个透传方法(invoke/stream/release),测试中的RecordingClient正是通过实现该接口来记录调用参数,这从侧面说明业务代码与真实 HTTP 客户端之间只隔着这层窄接口。
InferenceAffinityModelClient 做了什么
InferenceAffinityModelClient.java 继承BaseModelClient,在静态块中调用registerClientClass(...)完成自注册,CLIENT_NAME等于ProviderType.INFERENCE_AFFINITY.getValue()。它的核心行为包括:
- 请求构造与工具调用净化(
buildAndSanitizeParams):调用buildRequestParams组装 OpenAI 兼容请求体;随后对assistant角色的tool_calls做净化(只保留id、type="function"、index以及function.name/function.arguments字符串,丢弃多余字段,避免服务端解析失败)。 - 缓存共享参数:当
enableCacheSharing && sessionId非空时,在请求体顶层写入:
params.put("cache_sharing", true); params.put("cache_salt", sessionId);- 非流式调用:从 kwargs 中剥离
tracer_record_data、session_id、enable_cache_sharing三个控制字段后,POST 到<apiBase>/v1/chat/completions;按ModelClientConfig.maxRetries进行指数退避重试(每次失败后等待2^attempt秒),收到非 200 响应则抛出IOException。解析响应时支持choices[0].message的content、reasoning_content、tool_calls,以及usage.prompt_tokens_details.cached_tokens(计入UsageMetadata.cacheTokens),finishReason在无工具调用时为stop,有工具调用时为tool_calls。 - 流式调用:POST 同样的端点,逐行解析
data:前缀的 SSE 块,[DONE]结束;若配置了outputParser,则走streamWithParser边累积边解析。 - 缓存释放:构造
releaseParams(model、cache_salt=sessionId、cache_sharing=true、messages、messages_released_index,以及可选的tools/tools_released_index),POST 到/release_kv_cache,200 即视为成功。 - 图像 / 语音 / 视频生成接口(
generateImage/generateSpeech/generateVideo)在该客户端中返回null,说明此客户端专注对话与缓存管理,不承担多模态生成。
测试 InferenceAffinityModelClientTest.java 用录制式客户端验证了cache_sharing/cache_salt的注入、tool_calls 净化结果,以及prompt_tokens_details.cached_tokens的解析。
与 KVCacheManager 的协作:差异释放机制
InferenceAffinityModel的价值不止于"能带 session 调用",更在于它可以被 KVCacheManager.java 驱动做按需释放。协作流程(对应 API 文档 KVCacheManager.md):
KVCacheManager记录上一次发给推理端的ContextWindow;首次调用只缓存窗口,不触发释放。- 后续窗口变化时,
checkReleaseNeeded/firstChangedDecision分别比较消息列表与工具列表的首个差异索引,得到ReleaseDecision(shouldRelease, messagesReleasedIndex, toolsReleasedIndex)。 - 若判定需要释放,
KVCacheManager检查模型是否可释放:model instanceof ReleaseCapableModel或存在release(String, List, Integer, List, Integer)方法。InferenceAffinityModel实现了该接口,于是被回调release(sessionId, lastContextWindow.getMessages(), messagesReleasedIndex, tools, toolsReleasedIndex)。 - 释放失败不阻塞主流程,只记录 warning 日志。
一句话总结协作关系:KVCacheManager负责"何时释放"的差异检测,InferenceAffinityModel负责"怎么释放"的协议调用。
接入示例:配置、调用与释放
1. 构造模型
import com.openjiuwen.core.foundation.llm.InferenceAffinityModel; import com.openjiuwen.core.foundation.llm.schema.ModelClientConfig; import com.openjiuwen.core.foundation.llm.schema.ModelRequestConfig; ModelClientConfig clientConfig = ModelClientConfig.builder() .clientProvider("InferenceAffinity") // 内置 provider,也可写 inference_affinity .apiKey("sk-xxx") .apiBase("http://vllm-gateway:8000") // 服务根地址,不要拼 /chat/completions .timeout(60.0) .maxRetries(3) .verifySsl(true) .build(); ModelRequestConfig requestConfig = ModelRequestConfig.builder() .modelName("your-vllm-model") .temperature(0.6) .topP(0.8) .maxTokens(256) .build(); InferenceAffinityModel model = new InferenceAffinityModel(clientConfig, requestConfig);2. 携带 session 的非流式调用
import com.openjiuwen.core.foundation.llm.schema.AssistantMessage; import com.openjiuwen.core.foundation.llm.schema.SystemMessage; import com.openjiuwen.core.foundation.llm.schema.UserMessage; import java.util.List; import java.util.Map; List<Object> messages = List.of( new SystemMessage("你是一个简洁的助手。"), new UserMessage("你好,请记住会话 ID 对应的上下文。") ); AssistantMessage response = model.invoke( messages, // messages null, // tools null, // temperature(不覆盖默认) null, // topP null, // maxTokens null, // stop null, // model(回退 requestConfig.modelName) null, // outputParser "session-1", // sessionId:KV Cache 复用的关键 true, // enableCacheSharing:开启缓存共享 Map.of("custom", "value") // kwargs:额外参数 ).toCompletableFuture().join(); System.out.println(response.getContentAsString());开启enableCacheSharing=true后,请求体会携带cache_sharing: true与cache_salt: "session-1",服务端据此把该会话的 KV Cache 组织为可复用的缓存条目。
3. 流式调用
import com.openjiuwen.core.foundation.llm.schema.AssistantMessageChunk; import java.util.Iterator; Iterator<AssistantMessageChunk> chunks = model.stream( messages, null, null, null, null, null, null, null, "session-1", true, null); StringBuilder fullText = new StringBuilder(); while (chunks.hasNext()) { AssistantMessageChunk chunk = chunks.next(); String delta = chunk.getContentAsString(); if (delta != null && !delta.isEmpty()) { System.out.print(delta); fullText.append(delta); } }4. 主动释放缓存
Boolean released = model.release( "session-1", // sessionId messages, // 会话消息 1, // messagesReleasedIndex:释放起点 null, // tools null, // toolsReleasedIndex null // model:为空时回退 requestConfig.modelName ).toCompletableFuture().join();release会向/release_kv_cache发送包含cache_salt、messages_released_index的请求,让服务端释放该会话已缓存但不再需要的 KV Cache 前缀,为后续轮次腾出显存。测试releaseImplementsKvCacheManagerPort验证了 sessionId、messages、释放索引等参数的正确透传。
使用边界与注意事项
- provider 名称兼容:
ModelClientConfig.clientProvider支持大小写兼容匹配,但建议按内置名称原样填写InferenceAffinity或inference_affinity;写错会在构造阶段报 provider 不可用(MODEL_PROVIDER_INVALID)。 - 客户端能力边界:
InferenceAffinityModelClient的多模态生成方法返回null,本模型入口聚焦对话与 KV Cache 管理,不要用它承接图像 / 语音 / 视频生成。 - 与 Ascend-affinity 互斥:仓库文档 Ascend算力亲和.md 明确指出,Ascend-affinity 与 InferenceAffinity 的 release 模式互斥:affinity 客户端的
supportsKvCacheRelease()恒为 false,二者不可同时启用。需要设备亲和调度时应走 affinity 链路(extensions.kv_cache.mode=affinity+enableKvCacheAffinity),需要 vLLM 会话级缓存复用与释放时走本文的 InferenceAffinity 链路。 - 日志观测:请求发出与响应接收均有
Loggers.LLM日志(如InferenceAffinity API response received、Before release KV cache, release request params ready),排障时可先 grep 这些关键字确认请求是否真正发出、参数是否正确。 - 签名以源码为准:文档说明"所有签名均以当前 Java 源码为准",上文签名均取自 InferenceAffinityModel.java 与 InferenceAffinityModelClient.java;
invoke/stream/release实际返回CompletionStage,调用时注意通过.toCompletableFuture().join()或异步编排消费结果。
源码阅读地图
| 顺序 | 文件 | 建议关注点 |
|---|---|---|
| 1 | InferenceAffinityModel.java | 构造校验、normalizeKwargs、反射适配、ReleaseCapableModel实现 |
| 2 | InferenceAffinityModelClient.java | cache_sharing/cache_salt注入、tool_calls 净化、/release_kv_cache协议 |
| 3 | InferenceAffinityModelClientFactory.java 与 DefaultModelClientFactories.java | provider 工厂注册机制 |
| 4 | KVCacheManager.java | 窗口差异检测与释放回调 |
| 5 | InferenceAffinityModelTest.java 与 InferenceAffinityModelClientTest.java | 参数透传与协议构造的行为参照 |
相关文档可继续阅读:接入大模型.md(两层配置与 provider 列表)、KVCacheManager.md(差异释放语义)、Ascend算力亲和.md(affinity 链路与互斥边界)。
- 人工智能
- AI Agent
- Agent 框架
- 工具调用
- Agent 工作流
- RAG
- MCP Clients
- 强化学习
【免费下载链接】agent-core-java
openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。
相关推荐
agent-core-java 中 InferenceAffinityModelClient 的实现解析:KV Cache 共享、释放与 vLLM 会话亲和调用
agent core java 中 InferenceAffinityModelClient 的实现解析:KV Cache 共享、释放与 vLLM 会话亲和调用
人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习vllm-ascend SparseAttentionScore 算子深度解析:面向 Paged KV Cache 的 Top-K 稀疏注意力设计与实现
vllm ascend SparseAttentionScore 算子深度解析:面向 Paged KV Cache 的 Top K 稀疏注意力设计与实现 导读
人工智能大模型模型推理服务AscendCANNvLLM Hybrid KV Cache Manager 深度解析:混合注意力模型的 KV Cache 分配与前缀缓存设计
vLLM Hybrid KV Cache Manager 深度解析:混合注意力模型的 KV Cache 分配与前缀缓存设计 本篇技术指南基于 vLLM 官方设计
人工智能大模型模型推理服务推理引擎本地部署
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考