Curated chat history ends on an unanswered function_call when the turn after AFC is invalid

未关闭
#3,314 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
48/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
python

调研方向

Read _BaseChat.__init__ and trace _extract_curated_history plus the four send_message variants that handle AFC. Compare their history truncation with record_history and the behavior discussed in #3290; start with tests/chats/test_send_message.py using the replay setup. Done means the curated history and next request handle an invalid turn after a function call consistently, with tests covering the exchange.

由索引模型根据 Issue 内容生成。

描述

When AFC runs a tool in Chat.send_message and the next model turn is invalid, curated history keeps the function_call and drops the computed function_response.

Dropping the response matches the _BaseChat.__init__ docstring (invalid turns "and their associated inputs"). The call is my question: 2580638 ends a turn on one on purpose when the budget is spent, before any function runs. Here it ran. A hand-built history passed to chats.create curates to the same tail, and #3290 leaves both.

Environment details
  • Programming language: Python
  • OS: Linux x86_64
  • Language runtime version: CPython 3.12.14
  • Package version: google-genai 3.0.0, main at afb10e8 (2026-11-07), also #3290 head 3eaeefa
Steps to reproduce
  1. Run the script below. The stubbed generate_content returns a function_call, then an empty SAFETY candidate.
# afc_tail.py
from unittest import mock
from google import genai
from google.genai import types

def get_weather(city: str) -> dict:
    """Returns the weather for a city."""
    return {"c": 21}

def kinds(history):
    return [c.role + ":" + ("+".join(k for p in c.parts or [] for k in ("text", "function_call", "function_response")
                                     if getattr(p, k) is not None) or "empty") for c in history]

call = types.GenerateContentResponse(candidates=[types.Candidate(content=types.Content(
    role="model", parts=[types.Part.from_function_call(name="get_weather", args={"city": "Paris"})]))])
blocked = types.GenerateContentResponse(candidates=[types.Candidate(finish_reason="SAFETY")])
ok = types.GenerateContentResponse(candidates=[types.Candidate(content=types.Content(role="model", parts=[types.Part(text="ok")]))])

client = genai.Client(api_key="not-a-real-key")
chat = client.chats.create(model="gemini-3.8-flash", config=types.GenerateContentConfig(tools=[get_weather]))
sent = []
def fake(*, model, contents, config):
    sent.append(list(contents))
    return [call, blocked, ok][len(sent) - 1]
with mock.patch.object(chat._modules, "generate_content", side_effect=fake):
    chat.send_message("what is the weather in Paris")
    print("comprehensive:", kinds(chat.get_history()))
    print("curated:      ", kinds(chat.get_history(curated=True)))
    chat.send_message("try again")
    print("next request: ", kinds(sent[-1]))
$ python afc_tail.py      # main afb10e8; the same three lines at #3290 head 3eaeefa
comprehensive: ['user:text', 'model:function_call', 'user:function_response', 'model:empty']
curated:       ['user:text', 'model:function_call']
next request:  ['user:text', 'model:function_call', 'user:text']

With a trial truncation in sync send_message back to the curated length before the AFC loop:

$ python afc_tail.py      # main afb10e8 plus that truncation in Chat.send_message
curated:       []
next request:  ['user:text']
# not run: the live API, so I have not measured how the server answers that next request
# not run: tests/chats/test_send_message.py, which needs the replay setup

I would drop the whole exchange, as record_history already does with an invalid turn's input. The cost is that a retry may run the tool again. That needs the truncation in all four send_message variants, and _extract_curated_history popping through the call when it pops a function response. Is the tail intended once the function ran, or should the exchange go?

主要语言
Python
星标
4.1k
派生
1.1k
平均合并
1 天 21 小时
30 天内合并 PR
103

环境准备

  • 没有 Dockerfile 或 Docker Compose 文件
  • 没有 Pull Request 模板
  • 阅读贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

googleapis/python-genai 的其他 Issue

查看 googleapis/python-genai 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。