Files
hermes-agent/tests/agent/test_context_engine_message_uid.py
kshitijk4poor 0cabdf3f64 fix(sessions): an assistant fold ignores a non-dict uid map on the surviving turn too
The absorbed turn's map already had the dict guard; a plugin-built survivor carrying a malformed
map still raised in the per-occurrence expansion. Pre-existing (the old merge raised the same way).
2026-09-29 04:15:21 +05:30

504 lines
25 KiB
Python

"""A context engine sees ``message_uid`` on every message the host hands it.
The engine-facing surfaces are ``compress()`` (the live list), ``post_llm_call``'s
``conversation_history`` (the same live dicts after the turn flush) and ``on_turn_complete``
(structural clones). The id must reach all three after a cold restore that never asked for
``_row_id``, survive the persist override and an in-place compaction commit, and never reach the
provider. A consecutive-user merge keeps the first constituent's uid and records the absorbed ones.
"""
from __future__ import annotations
import logging
import os
from contextlib import closing
from pathlib import Path
from unittest.mock import patch
import pytest
from agent.context_engine import ContextEngine
from hermes_state import SessionDB
UID_LEN = 32
class _CapturingEngine(ContextEngine):
"""Records what compress() and on_turn_complete() receive; compress() returns marker-swept copies."""
last_prompt_tokens = 0
def __init__(self) -> None:
self.compress_input = None
self.turn_complete_messages = None
self._last_compress_aborted = False
self._last_summary_error = None
self.compression_count = 1
@property
def name(self) -> str:
return "capturing"
def update_from_response(self, usage):
pass
def should_compress(self, prompt_tokens=None):
return False
def compress(self, messages, current_tokens=None, focus_topic=None, force=False):
self.compress_input = [dict(m) for m in messages]
summary = {"role": "user", "content": "[CONTEXT COMPACTION] summary of prior turns"}
return [summary] + [dict(m) for m in messages[-2:]]
def on_turn_complete(self, messages, usage=None, **kwargs):
self.turn_complete_messages = messages
def _record_compression_failure_cooldown(self, *a, **k):
pass
def _make_agent(session_db, session_id):
with patch.dict(os.environ, {"OPENROUTER_API_KEY": "test-key"}):
from run_agent import AIAgent
agent = AIAgent(
api_key="test-key", base_url="https://openrouter.ai/api/v1", model="test/model", quiet_mode=True,
session_db=session_db, session_id=session_id, skip_context_files=True, skip_memory=True,
)
agent.compression_in_place = True
agent.context_compressor = _CapturingEngine()
return agent
def _seed(db, sid, n=6):
db.create_session(sid, "cli", model="test/model")
for i in range(n):
db.append_message(session_id=sid, role="user" if i % 2 == 0 else "assistant", content=f"seed msg {i}")
return [dict(r) for r in db._conn.execute(
"SELECT id, content, message_uid FROM messages WHERE session_id = ? AND active = 1 ORDER BY id",
(sid,)).fetchall()]
@pytest.fixture()
def db(tmp_path):
with closing(SessionDB(db_path=Path(tmp_path) / "state.db")) as handle:
yield handle
def test_compress_input_after_a_cold_restore_carries_every_rows_uid(db):
"""The ACP/gateway restore shape (no ``include_row_ids``) → compress() input → committed generation."""
from agent.conversation_compression import compress_context
sid = "20260928_120000_uid"
stored = _seed(db, sid)
restored = db.get_messages_as_conversation(sid, repair_alternation=True)
assert all("_row_id" not in m for m in restored)
agent = _make_agent(db, sid)
agent._session_db_created = True
agent._last_flushed_db_idx = len(restored)
compressed, _sp = compress_context(agent, restored, approx_tokens=100_000, system_message="sys")
engine = agent.context_compressor
assert [m.get("message_uid") for m in engine.compress_input] == [r["message_uid"] for r in stored]
# The committed generation: the copied tail keeps its uids, the summary got a fresh one.
active = [dict(r) for r in db._conn.execute(
"SELECT content, message_uid FROM messages WHERE session_id = ? AND active = 1 ORDER BY id", (sid,))]
assert [r["content"] for r in active] == [
"[CONTEXT COMPACTION] summary of prior turns", "seed msg 4", "seed msg 5"]
assert [r["message_uid"] for r in active[1:]] == [stored[4]["message_uid"], stored[5]["message_uid"]]
assert len(active[0]["message_uid"]) == UID_LEN
# And the live list the caller keeps carries the same uids (what the next compress()/post_llm_call sees).
assert [m.get("message_uid") for m in compressed] == [r["message_uid"] for r in active]
@pytest.mark.parametrize("in_place", [True, False], ids=["in_place", "rotation"])
def test_an_engine_authored_uid_survives_the_commit(db, in_place):
"""An engine that pre-stamps ``message_uid`` on the rows it emits gets that value written through the
commit (in place and on rotation), so it can recognise its own rows by uid afterwards."""
from agent.conversation_compression import compress_context
class _PreStamping(_CapturingEngine):
def compress(self, messages, current_tokens=None, focus_topic=None, force=False):
summary = {"role": "user", "content": "[CONTEXT COMPACTION] summary of prior turns",
"message_uid": "e" * UID_LEN}
return [summary] + [dict(m) for m in messages[-2:]]
sid = f"20260928_120050_{'inplace' if in_place else 'rotate'}"
stored = _seed(db, sid)
restored = db.get_messages_as_conversation(sid)
agent = _make_agent(db, sid)
agent.context_compressor = _PreStamping()
agent.compression_in_place = in_place
agent._session_db_created = True
agent._last_flushed_db_idx = len(restored)
compressed, _sp = compress_context(agent, restored, approx_tokens=100_000, system_message="sys")
committed_sid = agent.session_id
if not in_place:
assert committed_sid != sid # rotation published a child session
active = db.get_messages_as_conversation(committed_sid)
assert [m["content"] for m in active][:1] == ["[CONTEXT COMPACTION] summary of prior turns"]
assert active[0]["message_uid"] == "e" * UID_LEN
assert [m["message_uid"] for m in active[1:3]] == [stored[4]["message_uid"], stored[5]["message_uid"]]
assert [m.get("message_uid") for m in compressed[:3]] == [m["message_uid"] for m in active[:3]]
def test_turn_flush_stamps_uids_on_the_live_dicts_post_llm_call_hands_over(db):
"""``post_llm_call(conversation_history=list(messages))`` passes the live dicts; after the turn flush
every one of them carries the durable uid, including the current-turn user row."""
sid = "20260928_120100_flush"
db.create_session(sid, "cli", model="test/model")
agent = _make_agent(db, sid)
agent._session_db_created = True
messages = [{"role": "user", "content": "hello"}, {"role": "assistant", "content": "hi"}]
agent._persist_user_message_idx = 0
agent._persist_session(messages, conversation_history=None)
assert all(len(m.get("message_uid", "")) == UID_LEN for m in messages)
stored = [r[0] for r in db._conn.execute(
"SELECT message_uid FROM messages WHERE session_id = ? AND active = 1 ORDER BY id", (sid,))]
assert [m["message_uid"] for m in messages] == stored
def test_persist_override_keeps_the_uid(db):
"""The ACP/gateway persist override rewrites the current-turn user CONTENT in place; the id is the row's."""
sid = "20260928_120200_override"
db.create_session(sid, "cli", model="test/model")
agent = _make_agent(db, sid)
agent._session_db_created = True
messages = [{"role": "user", "content": "api-only variant with injected context"}]
agent._persist_user_message_idx = 0
agent._persist_user_message_override = "what the user typed"
agent._persist_session(messages, conversation_history=None)
uid = messages[0]["message_uid"]
agent._apply_persist_user_message_override(messages)
assert messages[0]["content"] == "what the user typed"
assert messages[0]["message_uid"] == uid
row = db._conn.execute(
"SELECT content, message_uid FROM messages WHERE session_id = ? AND active = 1", (sid,)).fetchone()
assert (row[0], row[1]) == ("what the user typed", uid)
def test_consecutive_user_merge_keeps_the_first_uid_and_records_the_absorbed_ones():
from agent.agent_runtime_helpers import _merge_consecutive_users
a = {"role": "user", "content": "first", "message_uid": "a" * UID_LEN}
b = {"role": "user", "content": "second", "message_uid": "b" * UID_LEN}
c = {"role": "user", "content": "third", "message_uid": "c" * UID_LEN}
merged, repairs = _merge_consecutive_users([a, b, c])
assert repairs == 2 and merged == [a]
assert a["content"] == "first\n\nsecond\n\nthird"
assert a["message_uid"] == "a" * UID_LEN
assert a["_absorbed_message_uids"] == ["b" * UID_LEN, "c" * UID_LEN]
# A restored (no ``_row_id``) absorbed dict still leaves its uid on the survivor.
assert "_absorbed_row_ids" not in a
def test_restart_after_a_merge_still_sees_the_composites_constituents(db):
"""A dangling user row, a restart, the next prompt merged into it, a turn flush, another restart: the
restored survivor names the absorbed row by uid instead of leaving the engine to parse ``\\n\\n``."""
sid = "20260928_120300_witness"
db.create_session(sid, "cli", model="test/model")
db.append_message(session_id=sid, role="user", content="unanswered before the crash")
dangling_uid = db.get_messages_as_conversation(sid)[0]["message_uid"]
# Restart: the ACP/gateway restore shape, then the next prompt lands and the pre-request repair merges.
history = db.get_messages_as_conversation(sid, repair_alternation=True, include_row_ids=True)
agent = _make_agent(db, sid)
agent._session_db_created = True
prompt = {"role": "user", "content": "next prompt"}
messages = history + [prompt]
agent._persist_user_message_idx = 1
agent._persist_session(messages, conversation_history=history) # the turn-start flush of the prompt
from agent.agent_runtime_helpers import repair_message_sequence
repair_message_sequence(agent, messages)
assert len(messages) == 1 and messages[0]["message_uid"] == dangling_uid
assert messages[0]["_absorbed_message_uids"] == [prompt["message_uid"]]
messages.append({"role": "assistant", "content": "reply"})
agent._persist_session(messages, conversation_history=None)
# Second restart.
again = db.get_messages_as_conversation(sid, repair_alternation=True)
survivor = next(m for m in again if m["message_uid"] == dangling_uid)
assert survivor["content"].startswith("unanswered before the crash")
assert prompt["message_uid"] in survivor["_absorbed_message_uids"]
def test_tool_result_flushed_after_its_call_pairs_through_the_live_list(db):
"""Tool round shape: the assistant(tool_calls) row is flushed first, the result in a later flush."""
sid = "20260928_120400_tool"
db.create_session(sid, "cli", model="test/model")
agent = _make_agent(db, sid)
agent._session_db_created = True
assistant = {"role": "assistant", "content": "",
"tool_calls": [{"id": "call_x", "type": "function", "function": {"name": "t", "arguments": "{}"}}]}
messages = [{"role": "user", "content": "q"}, assistant]
agent._persist_user_message_idx = 0
agent._persist_session(messages, conversation_history=None)
uid = assistant["_tool_call_uids"]["call_x"]
result = {"role": "tool", "content": "r", "tool_call_id": "call_x", "tool_name": "t"}
messages.append(result)
agent._persist_session(messages, conversation_history=None)
assert result["_tool_call_uid"] == uid
stored = db._conn.execute(
"SELECT tool_call_uid FROM messages WHERE session_id = ? AND role = 'tool'", (sid,)).fetchone()[0]
assert stored == uid
def test_the_live_list_walk_binds_a_result_to_the_nearest_call_that_names_it():
from agent.message_metadata import tool_call_uid_from_history
def call(uid=None):
msg = {"role": "assistant", "content": "",
"tool_calls": [{"id": "call_x", "type": "function", "function": {"name": "t", "arguments": "{}"}}]}
if uid:
msg["_tool_call_uids"] = {"call_x": uid}
return msg
result = {"role": "tool", "content": "r", "tool_call_id": "call_x"}
# Nearest call wins over an older one with the same provider id.
assert tool_call_uid_from_history([call("1" * UID_LEN), result, call("2" * UID_LEN), result], 3, {}) == "2" * UID_LEN
# A nearer call without a map (legacy) owns the result: no uid, never the older occurrence's.
assert tool_call_uid_from_history([call("1" * UID_LEN), result, call(), result], 3, {}) is None
# Never across a user turn.
assert tool_call_uid_from_history([call("1" * UID_LEN), {"role": "user", "content": "q"}, result], 2, {}) is None
def test_a_superseded_verification_candidate_is_not_a_witness_constituent():
"""Alternation repair DISCARDS a provisional verification candidate in favour of the final answer; its
row is retired like an absorbed row, but its uid is not a constituent of anything."""
from agent.agent_runtime_helpers import _merge_consecutive_assistants
candidate = {"role": "assistant", "content": "provisional", "finish_reason": "verification_required",
"message_uid": "p" * UID_LEN, "_row_id": 7}
final = {"role": "assistant", "content": "verified answer", "message_uid": "f" * UID_LEN}
merged, repairs = _merge_consecutive_assistants([candidate, final])
assert repairs == 1 and merged == [final]
assert final["message_uid"] == "f" * UID_LEN
assert "_absorbed_message_uids" not in final
assert final["_absorbed_row_ids"] == [7]
def test_assistant_merge_keeps_the_absorbed_turns_tool_call_uids():
from agent.agent_runtime_helpers import _merge_consecutive_assistants
first = {"role": "assistant", "content": "a",
"tool_calls": [{"id": "call_1", "type": "function", "function": {"name": "t", "arguments": "{}"}}],
"_tool_call_uids": {"call_1": "1" * UID_LEN}}
second = {"role": "assistant", "content": "b",
"tool_calls": [{"id": "call_2", "type": "function", "function": {"name": "t", "arguments": "{}"}}],
"_tool_call_uids": {"call_2": "2" * UID_LEN}}
merged, repairs = _merge_consecutive_assistants([first, second])
assert repairs == 1 and merged == [first]
assert first["_tool_call_uids"] == {"call_1": "1" * UID_LEN, "call_2": "2" * UID_LEN}
def _reused_call(content, occurrence_uid=None):
msg = {"role": "assistant", "content": content,
"tool_calls": [{"id": "call_x", "type": "function", "function": {"name": "t", "arguments": "{}"}}]}
if occurrence_uid:
msg["_tool_call_uids"] = {"call_x": occurrence_uid}
return msg
def test_assistant_merge_keeps_every_occurrence_of_a_reused_provider_id():
"""Two folded turns that reuse a provider id (llama.cpp) both keep their call AND their occurrence uid;
the ids on the wire are untouched, and a following result pairs with the nearest (later) call."""
from agent.agent_runtime_helpers import _merge_consecutive_assistants
from agent.message_metadata import index_tool_call_uids, resolve_tool_call_uid
first, second = _reused_call("", "1" * UID_LEN), _reused_call("", "2" * UID_LEN)
merged, repairs = _merge_consecutive_assistants([first, second])
assert repairs == 1 and merged == [first]
assert [tc["id"] for tc in first["tool_calls"]] == ["call_x", "call_x"]
assert first["_tool_call_uids"] == {"call_x": ["1" * UID_LEN, "2" * UID_LEN]}
index: dict = {}
index_tool_call_uids(index, first)
assert resolve_tool_call_uid(index, "call_x") == "2" * UID_LEN
def test_a_reused_id_merge_survives_rewrite_and_restore(db):
"""The per-occurrence map a fold produces is what the survivor's rewrite persists and a restore returns;
the result keeps the uid of the call it answered."""
from agent.agent_runtime_helpers import repair_message_sequence
sid = "20260928_120600_reused"
db.create_session(sid, "cli", model="test/model")
agent = _make_agent(db, sid)
agent._session_db_created = True
first, second = _reused_call("checking"), _reused_call("")
result = {"role": "tool", "content": "r", "tool_call_id": "call_x", "tool_name": "t"}
messages = [{"role": "user", "content": "q"}, first, second, result]
agent._persist_user_message_idx = 0
agent._persist_session(messages, conversation_history=None)
occurrences = [first["_tool_call_uids"]["call_x"], second["_tool_call_uids"]["call_x"]]
assert len(set(occurrences)) == 2 and result["_tool_call_uid"] == occurrences[1]
repair_message_sequence(agent, messages)
assert first["_tool_call_uids"] == {"call_x": occurrences}
agent._persist_session(messages, conversation_history=None)
again = db.get_messages_as_conversation(sid, repair_alternation=False)
stored = next(m for m in again if m["role"] == "assistant" and m.get("tool_calls"))
assert stored["_tool_call_uids"] == {"call_x": occurrences}
assert next(m for m in again if m["role"] == "tool")["_tool_call_uid"] == occurrences[1]
@pytest.mark.parametrize("kept, dropped", [
([{"type": "text", "text": "kept"}], "UNIQUE-DROPPED-TEXT"),
("kept", [{"type": "text", "text": "UNIQUE-DROPPED-PART"}]),
([{"type": "text", "text": "kept"}], [{"type": "text", "text": "UNIQUE-DROPPED-PART"}]),
])
def test_an_assistant_fold_that_discards_the_later_text_is_no_witness(kept, dropped):
"""Multimodal content is never joined: the later turn's row is retired, but its uid must not be
claimed as a constituent of text it does not contain."""
from agent.agent_runtime_helpers import _merge_consecutive_assistants
survivor = {"role": "assistant", "content": kept, "message_uid": "a" * UID_LEN}
later = {"role": "assistant", "content": dropped, "message_uid": "b" * UID_LEN, "_row_id": 9}
merged, repairs = _merge_consecutive_assistants([survivor, later])
assert repairs == 1 and merged == [survivor] and survivor["content"] == kept
assert "_absorbed_message_uids" not in survivor
assert survivor["_absorbed_row_ids"] == [9]
def test_a_fold_ignores_a_non_dict_uid_map_on_either_turn():
"""A plugin-built dict can carry a malformed map; the fold keeps the well-formed side instead of raising."""
from agent.agent_runtime_helpers import _merge_assistant_into
def turn(call_id, uids):
return {"role": "assistant", "content": "",
"tool_calls": [{"id": call_id, "type": "function", "function": {"name": "t", "arguments": "{}"}}],
"_tool_call_uids": uids}
prev, new = turn("X", "junk"), turn("Y", {"Y": "b" * 32})
_merge_assistant_into(prev, new)
assert prev["_tool_call_uids"] == {"Y": "b" * 32}
prev, new = turn("X", {"X": "a" * 32}), turn("Y", ["junk"])
_merge_assistant_into(prev, new)
assert prev["_tool_call_uids"] == {"X": "a" * 32}
def test_a_select_context_selection_is_stripped_before_the_provider():
"""The request copy is stripped BEFORE ``select_context``; an engine that hands back the
``conversation_messages`` clones must not put the ids back on the wire."""
from agent.conversation_loop import _apply_context_engine_selection
from agent.message_metadata import PERSISTENCE_ONLY_MESSAGE_FIELDS
class _Selecting(_CapturingEngine):
def select_context(self, api_messages, conversation_messages=None, incoming_message=None, budget_tokens=0):
return list(conversation_messages)
class _Agent:
context_compressor = _Selecting()
session_id = "s"
history = [
{"role": "user", "content": "q", "message_uid": "a" * UID_LEN, "timestamp": 1.0, "_row_id": 1,
"_absorbed_message_uids": ["b" * UID_LEN]},
{"role": "assistant", "content": "", "message_uid": "c" * UID_LEN, "timestamp": 2.0,
"tool_calls": [{"id": "call_x", "type": "function", "function": {"name": "t", "arguments": "{}"}}],
"_tool_call_uids": {"call_x": "d" * UID_LEN}},
{"role": "tool", "content": "r", "tool_call_id": "call_x", "message_uid": "e" * UID_LEN, "timestamp": 3.0,
"_tool_call_uid": "d" * UID_LEN},
]
api = [{k: v for k, v in m.items() if k not in PERSISTENCE_ONLY_MESSAGE_FIELDS} for m in history]
out = _apply_context_engine_selection(_Agent(), api, history, history[0], logger=logging.getLogger("t"))
assert [m["content"] for m in out] == ["q", "", "r"]
assert out[1]["tool_calls"][0]["id"] == "call_x"
assert not any(key in m for m in out for key in PERSISTENCE_ONLY_MESSAGE_FIELDS)
assert all(m["message_uid"] for m in history) # history untouched
def test_the_inflight_task_restated_onto_the_carrier_records_its_uid():
from agent.context_compressor import (
COMPRESSED_SUMMARY_METADATA_KEY, SUMMARY_PREFIX, _INFLIGHT_TASK_REPLAY_HEADER, _SUMMARY_END_MARKER,
ContextCompressor,
)
compressor = object.__new__(ContextCompressor)
compressor.quiet_mode = True
carrier = {"role": "user", "content": f"{SUMMARY_PREFIX}\n\nwhat happened\n\n{_SUMMARY_END_MARKER}",
COMPRESSED_SUMMARY_METADATA_KEY: True, "message_uid": "s" * UID_LEN}
inflight = {"role": "user", "content": "finish the task", "message_uid": "a" * UID_LEN}
out = compressor._reappend_inflight_user_task([carrier], inflight)
assert out == [carrier] and _INFLIGHT_TASK_REPLAY_HEADER in carrier["content"]
assert carrier["message_uid"] == "s" * UID_LEN
assert carrier["_absorbed_message_uids"] == ["a" * UID_LEN]
def test_the_real_user_anchor_folded_into_scaffolding_keeps_the_anchors_uid():
"""The anchor's text leads the composite, so its uid is the composite's; the scaffolding row's is absorbed."""
from agent.conversation_compression import _insert_real_user_anchor, _merge_anchor_into_user_message
target = {"role": "user", "content": "[todo snapshot]", "message_uid": "t" * UID_LEN}
anchor = {"role": "user", "content": "the real ask", "message_uid": "a" * UID_LEN,
"_absorbed_message_uids": ["b" * UID_LEN]}
_merge_anchor_into_user_message(target, anchor)
assert target["content"] == "the real ask\n\n[todo snapshot]"
assert target["message_uid"] == "a" * UID_LEN
assert target["_absorbed_message_uids"] == ["b" * UID_LEN, "t" * UID_LEN]
# Never-persisted scaffolding (no uid) absorbs nothing but still takes the anchor's identity.
compressed = [{"role": "user", "content": "earlier"}, {"role": "assistant", "content": "ok"},
{"role": "user", "content": "[todo snapshot]"}]
assert _insert_real_user_anchor(compressed, dict(anchor, _absorbed_message_uids=[])) == "merged"
assert compressed[-1]["message_uid"] == "a" * UID_LEN
assert "_absorbed_message_uids" not in compressed[-1]
def test_micro_compactions_adjacent_user_merge_records_the_witness():
from types import SimpleNamespace
from agent.micro_compaction import MicroCompactionMixin
first = {"role": "user", "content": "one", "message_uid": "1" * UID_LEN}
second = {"role": "user", "content": "two", "message_uid": "2" * UID_LEN}
merged = MicroCompactionMixin._merge_adjacent_user_turns(SimpleNamespace(), [first, second])
assert merged == [first] and first["content"] == "one\n\ntwo"
assert first["message_uid"] == "1" * UID_LEN
assert first["_absorbed_message_uids"] == ["2" * UID_LEN]
def test_an_assistant_merge_rewrite_persists_the_unioned_tool_call_uids(db):
"""Alternation repair unions two flushed assistant turns' tool calls; the survivor's row-addressed
rewrite must carry the unioned uid map (it follows ``tool_calls``), and a restore must return it."""
from agent.agent_runtime_helpers import repair_message_sequence
sid = "20260928_120500_union"
db.create_session(sid, "cli", model="test/model")
agent = _make_agent(db, sid)
agent._session_db_created = True
survivor = {"role": "assistant", "content": "let me check"}
caller = {"role": "assistant", "content": "",
"tool_calls": [{"id": "call_2", "type": "function", "function": {"name": "t", "arguments": "{}"}}]}
result = {"role": "tool", "content": "r", "tool_call_id": "call_2", "tool_name": "t"}
messages = [{"role": "user", "content": "q"}, survivor, caller, result]
agent._persist_user_message_idx = 0
agent._persist_session(messages, conversation_history=None) # both assistants flushed with a digest
uids = dict(caller["_tool_call_uids"])
assert set(uids) == {"call_2"} and "_tool_call_uids" not in survivor and result["_tool_call_uid"] == uids["call_2"]
repair_message_sequence(agent, messages)
assert messages == [messages[0], survivor, result] and survivor["_tool_call_uids"] == uids
agent._persist_session(messages, conversation_history=None)
assert survivor["_tool_call_uids"] == uids
again = db.get_messages_as_conversation(sid, repair_alternation=False)
stored = next(m for m in again if m["role"] == "assistant" and m["content"].startswith("let me check"))
assert [tc["id"] for tc in stored["tool_calls"]] == ["call_2"]
assert stored["_tool_call_uids"] == uids
assert next(m for m in again if m["role"] == "tool")["_tool_call_uid"] == uids["call_2"]