mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-23 22:44:59 +08:00
core/cli+agent: /compact and resume latest by workspace recency
Soft-compact history, model-summarize without tools, seed a new session; /resume with no id loads the lately-used session for this workspace.
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
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"),
|
||||
]
|
||||
out = soft_compact_transcript(messages, max_chars=500)
|
||||
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"
|
||||
assert isinstance(seed[1], UserChatMessage)
|
||||
assert "summary text" in seed[1].content
|
||||
assert "session 3" in seed[1].content
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user