@@ -215,6 +215,8 @@ func (s *Service) sendMessageInternal(
215215 var resolvedRoute * channel.ResolvedRoute
216216 var filteredOptions map [string ]interface {}
217217 var totalServerSideToolUsage map [string ]int64
218+ var responsesBackgroundRouteConfig llm.RouteConfig
219+ var responsesBackgroundRecovery openAIResponsesBackgroundRecoveryState
218220 userContentEstimatedInputTokens := int64 (0 )
219221 usageAccumulator := & messageUsageAccumulator {}
220222 upstreamCallStarted := false
@@ -224,6 +226,13 @@ func (s *Service) sendMessageInternal(
224226 runState .bind (& userMessage , & assistantMessage , & traceRecorder , & result , ctx )
225227 defer func () {
226228 if retErr != nil {
229+ if errors .Is (retErr , ErrMessageGenerationCanceled ) {
230+ if usage , ok := s .recoverOpenAIResponsesBackgroundUsage (responsesBackgroundRouteConfig , responsesBackgroundRecovery ); ok {
231+ if delta := diffLLMUsage (usage , responsesBackgroundRecovery .ObservedUsage ); delta != (llm.Usage {}) {
232+ usageAccumulator .addObservedUsage (delta )
233+ }
234+ }
235+ }
227236 if retained := s .persistInterruptedMessageGeneration (ctx , persistInterruptedMessageGenerationInput {
228237 SendInput : input ,
229238 UserMessage : userMessage ,
@@ -728,6 +737,7 @@ func (s *Service) sendMessageInternal(
728737 AttributionReferer : attributionReferer ,
729738 AttributionTitle : attributionTitle ,
730739 }
740+ responsesBackgroundRouteConfig = routeConfig
731741 filteredOptions = filterModelOptions (input .Options , route .Protocol , modelOptionPolicyConfig {
732742 Mode : cfg .ModelOptionPolicyMode ,
733743 AllowedPathsJSON : cfg .ModelOptionAllowedPaths ,
@@ -741,6 +751,10 @@ func (s *Service) sendMessageInternal(
741751 Tools : toolRuntime .definitions ,
742752 Options : filteredOptions ,
743753 }
754+ if supportsOpenAIResponsesBackgroundMode (route ) {
755+ generateInput .ResponsesBackground = true
756+ sendSpan .SetAttributes (attribute .Bool ("conversation.responses_background" , true ))
757+ }
744758 fullLLMMessages := llmMessages
745759 applyOpenAIResponsesInstructions (route , routeConfig .Endpoint , & generateInput )
746760 estimatedPromptTokens = estimateGenerateInputTokens (generateInput )
@@ -838,6 +852,11 @@ func (s *Service) sendMessageInternal(
838852 }
839853 callPromptShape := summarizePromptShape (callPromptMode , currentInput .Messages , currentInput .Messages , currentInput .PreviousResponseID )
840854 usageAccumulator .beginCall (currentInput )
855+ if currentInput .ResponsesBackground {
856+ responsesBackgroundRecovery = openAIResponsesBackgroundRecoveryState {Enabled : true }
857+ } else {
858+ responsesBackgroundRecovery = openAIResponsesBackgroundRecoveryState {}
859+ }
841860 generationCtx , generationSpan := platformtracing .Start (ctx , "conversation.llm.generate" ,
842861 trace .WithAttributes (append ([]attribute.KeyValue {
843862 attribute .Int64 ("conversation.id" , int64 (input .ConversationID )),
@@ -846,6 +865,7 @@ func (s *Service) sendMessageInternal(
846865 attribute .String ("llm.endpoint" , routeConfig .Endpoint ),
847866 attribute .Bool ("llm.stream" , streamRequested && streamSupported ),
848867 attribute .Bool ("llm.tools_disabled" , currentInput .DisableTools ),
868+ attribute .Bool ("llm.responses_background" , currentInput .ResponsesBackground ),
849869 attribute .Int ("llm.message_count" , len (currentInput .Messages )),
850870 attribute .Int ("llm.tool_count" , len (currentInput .Tools )),
851871 }, promptShapeTraceAttributes ("llm.prompt" , callPromptShape )... )... ),
@@ -908,6 +928,11 @@ func (s *Service) sendMessageInternal(
908928 callStreamUsage := llm.Usage {}
909929 upstreamCallStarted = true
910930 output , streamErr := s .llmClient .GenerateStream (generationCtx , routeConfig , currentInput , func (event llm.GenerateStreamEvent ) error {
931+ if currentInput .ResponsesBackground {
932+ if responseID := strings .TrimSpace (event .ResponseID ); responseID != "" {
933+ responsesBackgroundRecovery .ResponseID = responseID
934+ }
935+ }
911936 if s .isMessageGenerationCanceled (generationCtx , runID ) {
912937 return ErrMessageGenerationCanceled
913938 }
@@ -916,6 +941,9 @@ func (s *Service) sendMessageInternal(
916941 // 这里先换算成本次调用内增量,再累加成本轮消息总量,保证实时展示和最终账单口径一致。
917942 usageDelta := diffLLMUsage (event .Usage , callStreamUsage )
918943 callStreamUsage = event .Usage
944+ if currentInput .ResponsesBackground {
945+ responsesBackgroundRecovery .ObservedUsage = callStreamUsage
946+ }
919947 currentUsage := usageAccumulator .addObservedUsage (usageDelta )
920948 if input .OnEvent != nil {
921949 if err := emitLLMUsageEvent (input .OnEvent , currentUsage ); err != nil {
@@ -1011,6 +1039,25 @@ func (s *Service) sendMessageInternal(
10111039 if handleCanceledGeneration (err ) {
10121040 return nil , retErr
10131041 }
1042+ if err != nil && generateInput .ResponsesBackground &&
1043+ strings .TrimSpace (streamedText .String ()) == "" &&
1044+ shouldRetryWithoutResponsesBackground (err ) {
1045+ if s .logger != nil {
1046+ s .logger .Warn ("openai_responses_background_rejected_retry_standard" ,
1047+ zap .String ("trace_id" , traceid .FromContext (ctx )),
1048+ zap .Uint ("conversation_id" , input .ConversationID ),
1049+ zap .String ("protocol" , route .Protocol ),
1050+ zap .String ("upstream_name" , route .UpstreamName ),
1051+ zap .Error (err ),
1052+ )
1053+ }
1054+ generateInput .ResponsesBackground = false
1055+ responsesBackgroundRecovery = openAIResponsesBackgroundRecoveryState {}
1056+ upstreamOutput , err = runGenerate (generateInput )
1057+ if handleCanceledGeneration (err ) {
1058+ return nil , retErr
1059+ }
1060+ }
10141061 if err != nil && strings .TrimSpace (generateInput .PreviousResponseID ) != "" &&
10151062 strings .TrimSpace (streamedText .String ()) == "" &&
10161063 shouldRetryWithoutPreviousResponseID (err ) {
0 commit comments