test/stream/test_history_queries.py
Rama main · réplica de lectura del repositorio original.
# Copyright (c) 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0
import asyncio
from collections.abc import Iterable
import pytest
from ag2 import Context, MemoryStream
from ag2.context import StreamId
from ag2.events import BaseEvent, ModelRequest, ModelResponse, TextInput, ToolCallEvent, ToolCallsEvent
from ag2.history import MemoryStorage
class SpecialToolCall(ToolCallEvent):
pass
class RecordedStorage(MemoryStorage):
def __init__(self) -> None:
super().__init__()
self.reads = 0
async def get_history(self, stream_id: StreamId) -> Iterable[BaseEvent]:
self.reads += 1
return iter(tuple(await super().get_history(stream_id)))
class UnavailableStorage(MemoryStorage):
async def get_history(self, stream_id: StreamId) -> Iterable[BaseEvent]:
raise OSError("storage unavailable")
@pytest.mark.asyncio
async def test_history_selects_retained_events_in_publication_order() -> None:
stream = MemoryStream()
context = Context(stream)
first = ToolCallEvent(name="lookup", arguments="{}")
last = ToolCallEvent(name="lookup", arguments='{"page": 2}')
await context.send(first)
await context.send(ModelRequest([TextInput("question")]))
await context.send(last)
query = stream.history.where(ToolCallEvent)
assert await query.all() == [first, last]
assert await query.first() is first
assert await query.last() is last
assert await query.count() == 2
@pytest.mark.asyncio
async def test_history_query_combines_conditions_without_changing_original() -> None:
stream = MemoryStream()
context = Context(stream)
first = ToolCallEvent(name="lookup", arguments="{}")
second = SpecialToolCall(name="other", arguments="{}")
await context.send(first)
await context.send(second)
calls = stream.history.where(ToolCallEvent)
lookups = calls.where(ToolCallEvent.name == "lookup")
assert await lookups.all() == [first]
assert await calls.all() == [first, second]
assert await stream.history.where(ToolCallEvent.name == "other").all() == [second]
assert await lookups.where(ToolCallEvent.name == "other").all() == []
@pytest.mark.asyncio
async def test_empty_history_queries_complete_without_waiting_for_publication() -> None:
query = MemoryStream().history.where(ToolCallEvent)
assert await asyncio.wait_for(query.all(), timeout=1) == []
assert await asyncio.wait_for(query.first(), timeout=1) is None
assert await asyncio.wait_for(query.last(), timeout=1) is None
assert await asyncio.wait_for(query.count(), timeout=1) == 0
@pytest.mark.asyncio
async def test_history_queries_keep_top_level_occurrences_and_return_fresh_lists() -> None:
stream = MemoryStream()
context = Context(stream)
call = ToolCallEvent(name="lookup", arguments="{}")
batch = ToolCallsEvent([call])
await context.send(ModelResponse(tool_calls=batch))
await context.send(batch)
await context.send(call)
await context.send(call)
query = stream.history.where(ToolCallEvent)
selected = await query.all()
assert selected == [call, call]
selected.clear()
assert await query.count() == 2
assert await query.all() == [call, call]
assert len(list(await stream.history.get_events())) == 4
@pytest.mark.asyncio
async def test_history_query_uses_one_fresh_custom_storage_snapshot_per_terminal() -> None:
storage = RecordedStorage()
stream = MemoryStream(storage=storage)
context = Context(stream)
first = ToolCallEvent(name="first", arguments="{}")
second = ToolCallEvent(name="second", arguments="{}")
await context.send(first)
query = stream.history.where(ToolCallEvent).where(ToolCallEvent.name != "ignored")
assert storage.reads == 0
assert await query.all() == [first]
assert storage.reads == 1
await context.send(second)
assert await query.last() is second
assert storage.reads == 2
assert await query.first() is first
assert storage.reads == 3
assert await query.count() == 2
assert storage.reads == 4
await stream.history.replace([second])
restored = MemoryStream(storage=storage, id=stream.id)
assert await restored.history.where(ToolCallEvent).all() == [second]
@pytest.mark.asyncio
async def test_filtered_stream_query_uses_parent_history_without_live_filter() -> None:
stream = MemoryStream()
context = Context(stream)
request = ModelRequest([TextInput("question")])
call = ToolCallEvent(name="lookup", arguments="{}")
await context.send(request)
await context.send(call)
filtered = stream.where(ToolCallEvent)
assert await filtered.history.where(ModelRequest).all() == [request]
assert await filtered.history.where(BaseEvent).all() == [request, call]
@pytest.mark.asyncio
async def test_history_query_propagates_storage_errors() -> None:
query = MemoryStream(storage=UnavailableStorage()).history.where(BaseEvent)
with pytest.raises(OSError, match="storage unavailable"):
await query.count()