Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/kaos/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ requires = ["uv_build>=0.8.5,<0.9.0"]
build-backend = "uv_build"

[tool.uv.build-backend]
module-name = ["kaos"]
module-name = "kaos"
source-exclude = ["tests/**/*"]

[tool.ruff]
Expand Down
2 changes: 1 addition & 1 deletion packages/kosong/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ requires = ["uv_build>=0.8.5,<0.10.0"]
build-backend = "uv_build"

[tool.uv.build-backend]
module-name = ["kosong"]
module-name = "kosong"

[tool.ruff]
line-length = 100
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ requires = ["uv_build>=0.8.5,<0.10.0"]
build-backend = "uv_build"

[tool.uv.build-backend]
module-name = ["kimi_cli"]
module-name = "kimi_cli"
source-exclude = ["examples/**/*", "tests/**/*", "src/kimi_cli/deps/**/*"]

[tool.uv.workspace]
Expand Down
18 changes: 13 additions & 5 deletions src/kimi_cli/soul/compaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@

@runtime_checkable
class Compaction(Protocol):
async def compact(self, messages: Sequence[Message], llm: LLM) -> Sequence[Message]:
async def compact(
self, messages: Sequence[Message], llm: LLM, instruction: str | None = None
) -> Sequence[Message]:
"""
Compact a sequence of messages into a new sequence of messages.

Args:
messages (Sequence[Message]): The messages to compact.
llm (LLM): The LLM to use for compaction.
instruction (str | None): Optional custom summarization instruction.

Returns:
Sequence[Message]: The compacted messages.
Expand All @@ -42,8 +45,10 @@ class SimpleCompaction:
def __init__(self, max_preserved_messages: int = 2) -> None:
self.max_preserved_messages = max_preserved_messages

async def compact(self, messages: Sequence[Message], llm: LLM) -> Sequence[Message]:
compact_message, to_preserve = self.prepare(messages)
async def compact(
self, messages: Sequence[Message], llm: LLM, instruction: str | None = None
) -> Sequence[Message]:
compact_message, to_preserve = self.prepare(messages, instruction=instruction)
if compact_message is None:
return to_preserve

Expand Down Expand Up @@ -78,7 +83,7 @@ class PrepareResult(NamedTuple):
compact_message: Message | None
to_preserve: Sequence[Message]

def prepare(self, messages: Sequence[Message]) -> PrepareResult:
def prepare(self, messages: Sequence[Message], instruction: str | None = None) -> PrepareResult:
if not messages or self.max_preserved_messages <= 0:
return self.PrepareResult(compact_message=None, to_preserve=messages)

Expand Down Expand Up @@ -111,5 +116,8 @@ def prepare(self, messages: Sequence[Message]) -> PrepareResult:
compact_message.content.extend(
part for part in msg.content if not isinstance(part, ThinkPart)
)
compact_message.content.append(TextPart(text="\n" + prompts.COMPACT))
prompt_tail = prompts.COMPACT
if instruction:
prompt_tail = f"{prompts.COMPACT}\n\nUser instruction: {instruction}"
compact_message.content.append(TextPart(text="\n" + prompt_tail))
return self.PrepareResult(compact_message=compact_message, to_preserve=to_preserve)
8 changes: 6 additions & 2 deletions src/kimi_cli/soul/kimisoul.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ async def _grow_context(self, result: StepResult, tool_results: list[ToolResult]
await self._context.append_message(tool_messages)
# token count of tool results are not available yet

async def compact_context(self) -> None:
async def compact_context(self, instruction: str | None = None) -> None:
"""
Compact the context.

Expand All @@ -379,7 +379,11 @@ async def compact_context(self) -> None:
async def _compact_with_retry() -> Sequence[Message]:
if self._runtime.llm is None:
raise LLMNotSet()
return await self._compaction.compact(self._context.history, self._runtime.llm)
return await self._compaction.compact(
self._context.history,
self._runtime.llm,
instruction=instruction,
)

wire_send(CompactionBegin())
compacted_messages = await _compact_with_retry()
Expand Down
8 changes: 5 additions & 3 deletions src/kimi_cli/soul/slash.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,16 @@ async def init(soul: KimiSoul, args: list[str]):

@registry.command
async def compact(soul: KimiSoul, args: list[str]):
"""Compact the context"""
"""Compact context; optional hint via `/compact <summary hint>` (e.g. summarize code only)"""
if soul.context.n_checkpoints == 0:
wire_send(TextPart(text="The context is empty."))
return

instruction = " ".join(args).strip() if args else ""
logger.info("Running `/compact`")
await soul.compact_context()
wire_send(TextPart(text="The context has been compacted."))
await soul.compact_context(instruction if instruction else None)
suffix = f' with instruction: "{instruction}"' if instruction else ""
wire_send(TextPart(text=f"The context has been compacted{suffix}."))


@registry.command
Expand Down
23 changes: 23 additions & 0 deletions tests/test_simple_compaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,26 @@ def test_prepare_builds_compact_message_and_preserves_tail():
Message(role="assistant", content=[TextPart(text="Latest answer")]),
]
)


def test_prepare_injects_custom_instruction():
messages = [
Message(role="user", content=[TextPart(text="Old question")]),
Message(role="assistant", content=[TextPart(text="Old answer")]),
Message(role="user", content=[TextPart(text="Latest question")]),
Message(role="assistant", content=[TextPart(text="Latest answer")]),
]

instruction = "Summarize code only"
result = SimpleCompaction(max_preserved_messages=1).prepare(messages, instruction=instruction)

assert result.compact_message is not None
compact_tail = result.compact_message.content[-1]
assert isinstance(compact_tail, TextPart)
assert compact_tail.text == snapshot(
"\n"
+ prompts.COMPACT
+ """

User instruction: Summarize code only"""
)