2026-07-15 10:03:22 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from typing import TYPE_CHECKING, Literal, overload
|
|
|
|
|
|
|
|
|
|
from plyngent.agent.compact import (
|
|
|
|
|
build_compacted_seed_messages,
|
|
|
|
|
format_transcript,
|
|
|
|
|
soft_compact_transcript,
|
|
|
|
|
summarize_messages,
|
|
|
|
|
)
|
|
|
|
|
from plyngent.lmproto.openai_compatible.model import (
|
|
|
|
|
AssistantChatMessage,
|
|
|
|
|
ChatCompletionChoice,
|
|
|
|
|
ChatCompletionChunk,
|
|
|
|
|
ChatCompletionResponse,
|
|
|
|
|
ChatCompletionsParam,
|
|
|
|
|
SystemChatMessage,
|
|
|
|
|
ToolChatMessage,
|
|
|
|
|
UserChatMessage,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from collections.abc import AsyncIterator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_format_transcript() -> None:
|
|
|
|
|
text = format_transcript(
|
|
|
|
|
[
|
|
|
|
|
UserChatMessage(content="hi"),
|
|
|
|
|
AssistantChatMessage(content="yo"),
|
|
|
|
|
ToolChatMessage(content="out", tool_call_id="1"),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
assert "[user] hi" in text
|
|
|
|
|
assert "[assistant] yo" in text
|
|
|
|
|
assert "[tool 1] out" in text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_soft_compact_transcript_shrinks_tools() -> None:
|
|
|
|
|
big = "Z" * 2000
|
|
|
|
|
messages = [
|
|
|
|
|
UserChatMessage(content="u"),
|
|
|
|
|
ToolChatMessage(content=big, tool_call_id="1"),
|
|
|
|
|
ToolChatMessage(content="recent", tool_call_id="2"),
|
|
|
|
|
]
|
2026-07-15 10:58:01 +08:00
|
|
|
out = soft_compact_transcript(messages, max_tokens=100)
|
2026-07-15 10:03:22 +08:00
|
|
|
assert "truncated" in out or len(out) < len(big) + 50
|
|
|
|
|
assert "recent" in out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_build_compacted_seed_messages() -> None:
|
|
|
|
|
seed = build_compacted_seed_messages("summary text", system_prompt="sys", source_session_id=3)
|
|
|
|
|
assert isinstance(seed[0], SystemChatMessage)
|
|
|
|
|
assert seed[0].content == "sys"
|
2026-07-15 11:27:29 +08:00
|
|
|
assert isinstance(seed[1], AssistantChatMessage)
|
2026-07-15 11:36:01 +08:00
|
|
|
content = seed[1].content
|
|
|
|
|
assert isinstance(content, str)
|
|
|
|
|
assert "summary text" in content
|
|
|
|
|
assert "session 3" in content
|
2026-07-15 10:03:22 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class SummaryClient:
|
|
|
|
|
last: ChatCompletionsParam | None
|
|
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
self.last = None
|
|
|
|
|
|
|
|
|
|
@overload
|
|
|
|
|
async def chat_completions(
|
|
|
|
|
self, param: ChatCompletionsParam, *, stream: Literal[False] = False
|
|
|
|
|
) -> ChatCompletionResponse: ...
|
|
|
|
|
|
|
|
|
|
@overload
|
|
|
|
|
async def chat_completions(
|
|
|
|
|
self, param: ChatCompletionsParam, *, stream: Literal[True]
|
|
|
|
|
) -> AsyncIterator[ChatCompletionChunk]: ...
|
|
|
|
|
|
|
|
|
|
async def chat_completions(
|
|
|
|
|
self, param: ChatCompletionsParam, *, stream: bool = False
|
|
|
|
|
) -> ChatCompletionResponse | AsyncIterator[ChatCompletionChunk]:
|
|
|
|
|
del stream
|
|
|
|
|
self.last = param
|
|
|
|
|
return ChatCompletionResponse(
|
|
|
|
|
id="1",
|
|
|
|
|
object="chat.completion",
|
|
|
|
|
created=0,
|
|
|
|
|
model="t",
|
|
|
|
|
choices=[
|
|
|
|
|
ChatCompletionChoice(
|
|
|
|
|
index=0,
|
|
|
|
|
message=AssistantChatMessage(content=" done summary "),
|
|
|
|
|
logprobs={},
|
|
|
|
|
finish_reason="stop",
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
system_fingerprint="",
|
|
|
|
|
usage={},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_summarize_messages() -> None:
|
|
|
|
|
client = SummaryClient()
|
|
|
|
|
summary = await summarize_messages(
|
|
|
|
|
client,
|
|
|
|
|
[UserChatMessage(content="hello"), AssistantChatMessage(content="world")],
|
|
|
|
|
model="m",
|
|
|
|
|
)
|
|
|
|
|
assert summary == "done summary"
|
|
|
|
|
assert client.last is not None
|
|
|
|
|
assert client.last.model == "m"
|
|
|
|
|
from msgspec import UNSET
|
|
|
|
|
|
|
|
|
|
assert client.last.tools is UNSET
|