Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Python: extract_range() drops multi-call assistant message when filtering out just one of its parallel tool results

オープン
#14,485 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 2 日以内に返信

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
78/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
python
領域
backend

調査の方向性

Start in python/semantic_kernel/contents/history_reducer/chat_history_reducer_utils.py by reading extract_range() and get_call_result_pairs(), then run the parallel-call reproduction from the issue. Update the pair association so one assistant call index accounts for all its result indices; done means filtering one result preserves the call and its other result without orphaning either message.

索引モデルが issue の本文から書いたものです。

説明

python triage

Describe the bug

extract_range() in python/semantic_kernel/contents/history_reducer/chat_history_reducer_utils.py is used by ChatHistorySummarizationReducer / ChatHistoryTruncationReducer (via preserve_pairs=True) to make sure a function call is never separated from its result when the history is reduced.

The pair lookup is built like this (current main):

pair_map = {}
if preserve_pairs:
    pairs = get_call_result_pairs(history)
    for cidx, ridx in pairs:
        pair_map[cidx] = ridx
        pair_map[ridx] = cidx

get_call_result_pairs() returns one (call_index, result_index) tuple per function call, so when a single assistant message contains more than one FunctionCallContent (the normal shape for parallel/multi tool calling), several pairs share the same call_index. Because pair_map is a plain dict, pair_map[cidx] = ridx silently overwrites earlier entries, so only the last result for that call index survives in the forward direction. The reverse entries (pair_map[ridx] = cidx) don't collide, so each result still points back at the call correctly - only the call's own forward pointer is wrong.

Later in extract_range, when the call message's own turn comes up, its keep/skip decision (and the filter_func "skip both" check) is made using that single, possibly-wrong paired_idx, not the full set of results tied to that call. If a filter_func is used to drop just one of several parallel tool results, the call message ends up being evaluated only against the result that happens to still be in pair_map[cidx], which can cause the call to be dropped even though another of its own results is being kept - leaving a TOOL result message in the output with no corresponding assistant tool_calls message at all.

To Reproduce

Ran this against the actual installed semantic-kernel package (pip show semantic-kernel -> 1.44.1; diffed byte-for-byte identical to the current main copy of chat_history_reducer_utils.py before running):

from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.function_call_content import FunctionCallContent
from semantic_kernel.contents.function_result_content import FunctionResultContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.contents.history_reducer.chat_history_reducer_utils import extract_range, get_call_result_pairs

msg0 = ChatMessageContent(role=AuthorRole.USER, content="weather?")
msg1 = ChatMessageContent(
    role=AuthorRole.ASSISTANT,
    items=[
        FunctionCallContent(id="call_paris", name="get_weather", arguments='{"city":"Paris"}'),
        FunctionCallContent(id="call_tokyo", name="get_weather", arguments='{"city":"Tokyo"}'),
    ],
)
msg2 = ChatMessageContent(role=AuthorRole.TOOL, items=[FunctionResultContent(id="call_paris", name="get_weather", result="15C")])
msg3 = ChatMessageContent(role=AuthorRole.TOOL, items=[FunctionResultContent(id="call_tokyo", name="get_weather", result="22C")])
msg4 = ChatMessageContent(role=AuthorRole.ASSISTANT, content="done")
history = [msg0, msg1, msg2, msg3, msg4]

print(get_call_result_pairs(history))
# [(1, 2), (1, 3)]  <- both pairs share call_index 1

filter_func = lambda m: m is msg3   # drop only the Tokyo result, e.g. a moderation/error filter

out = extract_range(history, start=0, end=len(history), filter_func=filter_func, preserve_pairs=True)
for m in out:
    print(m.role, [getattr(it, "id", None) for it in m.items] if m.items else m.content)

Output:

AuthorRole.USER weather?
AuthorRole.TOOL ['call_paris']
AuthorRole.ASSISTANT done

The assistant message with the two tool_calls (msg1) is gone entirely, even though call_paris's own result (msg2) is kept right after it with no call message in front of it.

Expected behavior

Filtering out only call_tokyo's result should not affect call_paris's pair. Expected output keeps the call message and the Paris result together, and drops only the Tokyo result:

AuthorRole.USER weather?
AuthorRole.ASSISTANT [call_paris, call_tokyo]   (or with call_tokyo's FunctionCallContent stripped)
AuthorRole.TOOL ['call_paris']
AuthorRole.ASSISTANT done

At minimum, the call message and call_paris's result should never both disappear/appear inconsistently relative to each other - right now the call is dropped solely because of an unrelated sibling call's filtered result.

Platform

  • Language: Python
  • Source: pip package semantic-kernel==1.44.1, and confirmed the relevant file is unchanged on the current main branch
  • File: python/semantic_kernel/contents/history_reducer/chat_history_reducer_utils.py, function extract_range() (the pair_map construction) together with get_call_result_pairs()

Additional context

This is a distinct root cause from the interleaved-messages reordering bug already reported and fixed in the still-open #14165 (Fix extract_range reordering messages when preserving function call/result pairs). I checked that PR's rewritten implementation and it keeps the exact same pair_map[cidx] = ridx / pair_map[ridx] = cidx construction, so this dict-collision bug reproduces identically against that PR's version of the function too - it is not fixed as a side effect of #14165 and needs its own fix in how pair_map associates a call index with all of its result indices (e.g. pair_map[cidx] holding a list of result indices instead of a single int).

Related but different: #12708 and #13062 (both closed by the stale bot, not fixed) describe tool-call/result pairs being orphaned when a pair straddles the [start, end) boundary. This report is about pairs being mishandled even when everything is fully inside the requested range, purely because more than one result shares a call index.

主要言語
C#
スター
28.6k
フォーク
4.8k
平均マージ
13時間 24分
マージ済み PR(30日)
11

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

microsoft/semantic-kernel のほかの issue

microsoft/semantic-kernel の issue をすべて見る

似ている issue

C# の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。