1. 聊天模型可观测性(Chat Model Observability)
部分ChatModel/StreamingChatModel实现(见「Observability」列)支持配置ChatModelListener,监听三类事件:
| 事件 | 回调方法 | 时机 |
|---|---|---|
| 请求 | onRequest(ChatModelRequestContext) | 调用 LLM 提供商 API之前 |
| 响应 | onResponse(ChatModelResponseContext) | 成功收到响应后立即调用 |
| 错误 | onError(ChatModelErrorContext) | 出错时(仅一次) |
事件属性遵循 OpenTelemetry Generative AI 语义约定。
事件包含的属性
请求(Request)
- 消息(messages)
- 模型(model)
- 温度(Temperature)
- Top P
- 最大 tokens 数
- 工具(Tools)
- 响应格式(responseFormat)
- 等等
响应(Response)
- 助手消息(AiMessage)
- ID
- 模型
- Token 使用情况(TokenUsage)
- 结束原因(finishReason)
- 等等
2. ChatModelListener 完整示例
ChatModelListenerlistener=newChatModelListener(){@OverridepublicvoidonRequest(ChatModelRequestContextrequestContext){ChatRequestchatRequest=requestContext.chatRequest();List<ChatMessage>messages=chatRequest.messages();System.out.println(messages);ChatRequestParametersparameters=chatRequest.parameters();System.out.println(parameters.modelName());System.out.println(parameters.temperature());System.out.println(parameters.topP());System.out.println(parameters.topK());System.out.println(parameters.frequencyPenalty());System.out.println(parameters.presencePenalty());System.out.println(parameters.maxOutputTokens());System.out.println(parameters.stopSequences());System.out.println(parameters.toolSpecifications());System.out.println(parameters.toolChoice());System.out.println(parameters.responseFormat());// 提供商特有参数(以 OpenAI 为例,需 instanceof 判断)if(parametersinstanceofOpenAiChatRequestParametersopenAiParameters){System.out.println(openAiParameters.maxCompletionTokens());System.out.println(openAiParameters.logitBias());System.out.println(openAiParameters.parallelToolCalls());System.out.println(openAiParameters.seed());System.out.println(openAiParameters.user());System.out.println(openAiParameters.store());System.out.println(openAiParameters.metadata());System.out.println(openAiParameters.serviceTier());System.out.println(openAiParameters.reasoningEffort());}System.out.println(requestContext.modelProvider());// 在 onRequest / onResponse / onError 之间传递数据Map<Object,Object>attributes=requestContext.attributes();attributes.put("my-attribute","my-value");}@OverridepublicvoidonResponse(ChatModelResponseContextresponseContext){ChatResponsechatResponse=responseContext.chatResponse();AiMessageaiMessage=chatResponse.aiMessage();System.out.println(aiMessage);ChatResponseMetadatametadata=chatResponse.metadata();System.out.println(metadata.id());System.out.println(metadata.modelName());System.out.println(metadata.finishReason());if(metadatainstanceofOpenAiChatResponseMetadataopenAiMetadata){System.out.println(openAiMetadata.created());System.out.println(openAiMetadata.serviceTier());System.out.println(openAiMetadata.systemFingerprint());}TokenUsagetokenUsage=metadata.tokenUsage();System.out.println(tokenUsage.inputTokenCount());System.out.println(tokenUsage.outputTokenCount());System.out.println(tokenUsage.totalTokenCount());if(tokenUsageinstanceofOpenAiTokenUsageopenAiTokenUsage){System.out.println(openAiTokenUsage.inputTokensDetails().cachedTokens());System.out.println(openAiTokenUsage.outputTokensDetails().reasoningTokens());}ChatRequestchatRequest=responseContext.chatRequest();System.out.println(chatRequest);System.out.println(responseContext.modelProvider());Map<Object,Object>attributes=responseContext.attributes();System.out.println(attributes.get("my-attribute"));}@OverridepublicvoidonError(ChatModelErrorContexterrorContext){Throwableerror=errorContext.error();error.printStackTrace();ChatRequestchatRequest=errorContext.chatRequest();System.out.println(chatRequest);System.out.println(errorContext.modelProvider());Map<Object,Object>attributes=errorContext.attributes();System.out.println(attributes.get("my-attribute"));}};ChatModelmodel=OpenAiChatModel.builder().apiKey(System.getenv("OPENAI_API_KEY")).modelName(GPT_4_O_MINI).listeners(List.of(listener)).build();model.chat("Tell me a joke about Java");attributes 映射的作用
attributes(Map<Object, Object>)用于:
- 在同一个
ChatModelListener的onRequest→onResponse/onError之间传递信息(如请求开始时间、traceId)。 - 在多个
ChatModelListener之间传递信息。
3. 监听器的工作方式(重要规则)
调用顺序与线程
- 监听器以
List<ChatModelListener>形式配置,按迭代顺序依次调用。 - 同步调用,运行在同一个线程(流式场景例外,见下)。
- 第一个监听器返回之前,第二个不会被调用。
调用次数
onRequest():每次请求只调用一次;LLM API 出错重试时,不会为每次重试重复调用。onResponse():只调用一次,成功收到响应后立即调用。onError():只调用一次;重试时不会为每次重试重复调用。
异常处理
- 监听器方法抛出的异常以WARN级别记录日志。
- 后续监听器照常继续执行,不会中断。
请求内容是“最终请求”
- 三个 Context 中拿到的
ChatRequest是合并后的最终请求:ChatModel上配置的默认ChatRequestParameters+ 请求特定的ChatRequestParameters。
StreamingChatModel 的特殊点
onResponse()/onError()与onRequest()在不同线程中执行。- 线程上下文(如 MDC、ThreadLocal)不会自动传播→ 需要用
attributes把数据从onRequest()传到onResponse()/onError()。 - 时序:
onResponse()早于StreamingChatResponseHandler.onCompleteResponse()。onError()早于StreamingChatResponseHandler.onError()。
4. 在 Spring Boot 应用中的可观测性
原文此处为小节标题,展开内容待补充。
实践要点(结合上文规则的落地建议):
- 把
ChatModelListener注册为 Spring Bean(如@Component或@Bean),在构建ChatModel时通过.listeners(...)注入,便于统一收集指标/日志。 - 在
onRequest()中把traceId、开始时间等放入attributes,在onResponse()/onError()中取出,配合 Micrometer / OpenTelemetry 上报:- 耗时:
onResponse/onError时间戳 −onRequest时间戳 - Token 消耗:
tokenUsage.inputTokenCount()/outputTokenCount()/totalTokenCount() - 错误率:
onError计数 +errorContext.error()
- 耗时:
- 监听器内不要抛异常(异常只会被 WARN 吞掉),应自行 try-catch 并落日志,避免影响监控链路完整性。
速查小结
- 三个回调:
onRequest(请求前,一次)、onResponse(成功响应,一次)、onError(出错,一次);重试不会重复触发。 - 一个桥梁:
attributes映射 —— 跨回调、跨监听器传值;流式场景线程不同,必须靠它传递上下文。 - 两类参数:通用
ChatRequestParameters/ChatResponseMetadata+ 提供商特有(如OpenAiChatRequestParameters、OpenAiChatResponseMetadata、OpenAiTokenUsage),用instanceof取。 - 线程:非流式同线程同步;流式
onResponse/onError在另一线程,且早于StreamingChatResponseHandler对应回调。 - 容错:监听器抛异常只记 WARN,不影响后续监听器与主流程。
- 语义约定:属性命名对齐 OpenTelemetry Generative AI 语义约定,便于接入现有 APM。