Hacktoberfest 2026:維護者為十月標記出來的 issue,仍然開放、適合新手。 瀏覽 Hacktoberfest issue

LiteLlm with SSE streaming records the answer twice in gen_ai.output.messages

未關閉 適合新手
#7,630 3 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 4 天內回覆

還沒有人認領這個 Issue。

評估

難度
2/5
預估耗時
1-3 小時
新手友好度
78/100
Issue 類型
缺陷
描述清晰度
描述清楚
活躍度
活躍
技術堆疊
python

研究方向

從 src/google/adk/telemetry/_instrumentation.py 開始,特別查看 record_llm_response,並比較它如何處理串流傳輸的部分回應與彙總後的回應。接著閱讀 src/google/adk/models/lite_llm.py 中連結行附近的 LiteLlm 串流程式碼。使用提供的 StreamingMode.SSE 範例重現,並驗證擷取的輸出包含一則完整的回答訊息,與 StreamingMode.NONE 相符。

由索引模型根據 Issue 內容生成。

描述

🔴 Required Information

Describe the Bug:
with StreamingMode.SSE and a LiteLlm model the generate_content span ends up with every streamed chunk in gen_ai.output.messages and then the full answer again at the end. so the answer shows up twice and all the chunks have an empty finish_reason. without streaming its one message like expected. only visible with message content capture on (OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT)

looks like the text chunks get yielded as partial responses with no finish reason (lite_llm.py#L4475-L4485) and only the final aggregated response gets one (#L4556-L4558). record_llm_response keeps recording until it sees a finish reason (_instrumentation.py#L568-L595) so every chunk plus the aggregate goes in. since its in the LiteLlm wrapper i'd expect real providers to do the same but i only tested with litellm's mock_response

Steps to Reproduce:

  1. pip install google-adk litellm
  2. run this (no api key needed, it uses litellm's mock_response)
import asyncio
import json
import os

os.environ["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"] = "SPAN_ONLY"

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter

spans = InMemorySpanExporter()
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(spans))
trace.set_tracer_provider(provider)

from google.adk.agents import Agent
from google.adk.agents.run_config import RunConfig, StreamingMode
from google.adk.models.lite_llm import LiteLlm
from google.adk.runners import InMemoryRunner
from google.genai import types


async def run(mode):
  spans.clear()
  model = LiteLlm(model="openai/gpt-4o", mock_response="Oslo is 4C and cloudy today.", api_key="unused")
  runner = InMemoryRunner(agent=Agent(name="a", model=model), app_name="p")
  session = await runner.session_service.create_session(app_name="p", user_id="u")
  msg = types.Content(role="user", parts=[types.Part(text="weather?")])
  async for _ in runner.run_async(user_id="u", session_id=session.id, new_message=msg,
                                  run_config=RunConfig(streaming_mode=mode)):
    pass
  await runner.close()
  span = next(s for s in spans.get_finished_spans() if s.name.startswith("generate_content"))
  messages = json.loads(span.attributes["gen_ai.output.messages"])
  print(f"{mode.name}: {len(messages)} output message(s)")
  for m in messages:
    print("  ", repr(m.get("finish_reason")), "".join(p.get("content", "") for p in m["parts"]))


asyncio.run(run(StreamingMode.NONE))
asyncio.run(run(StreamingMode.SSE))

Expected Behavior:
one output message with the full answer, same as without streaming

Observed Behavior:

NONE: 1 output message(s)
   'stop' Oslo is 4C and cloudy today.
SSE: 11 output message(s)
   '' Osl
   '' o i
   '' s 4
   '' C a
   '' nd
   '' clo
   '' udy
   ''  to
   '' day
   '' .
   'stop' Oslo is 4C and cloudy today.

Environment Details:

  • ADK Library Version (pip show google-adk): 2.12.0 (main at 77a74f0)
  • Desktop OS: macOS
  • Python Version (python -V): 3.12.15

Model Information:

  • Are you using LiteLLM: Yes
  • Which model is being used: openai/gpt-4o via litellm mock_response

🟡 Optional Information

Regression:
havent checked older versions

maybe skip the partial chunks when recording output messages and keep just the aggregate? happy to send a PR if that sounds right

主要語言
Python
星號
21.9k
分支
4.2k
平均合併
3 天 16 小時
30 天內合併 PR
6

環境準備

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

google/adk-python 的其他 Issue

查看 google/adk-python 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。