给用量行挂上轮次步骤和分段字数,统计页能看出缓存断在哪。 Co-authored-by: Cursor <cursoragent@cursor.com>
115 lines
3.5 KiB
TypeScript
115 lines
3.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import {
|
||
annotateCacheBreaks,
|
||
firstCacheBreak,
|
||
outlineRequest,
|
||
} from "../src/llm/context-outline.js";
|
||
|
||
const sample = [
|
||
"规则开头",
|
||
"",
|
||
"## 写入规则",
|
||
"固定规则",
|
||
"",
|
||
"## 更早的对话",
|
||
"",
|
||
"【用户】",
|
||
"第一句",
|
||
"",
|
||
"【助手】",
|
||
"回过",
|
||
"",
|
||
"## 当前产物(已写入黑板)",
|
||
"",
|
||
"### 设计.氛围 〔order=0〕",
|
||
"雨",
|
||
"",
|
||
"### 设计.角色 〔order=1〕",
|
||
"甲",
|
||
"",
|
||
"## 本轮注入",
|
||
"",
|
||
"### topology @ 3",
|
||
"#### task",
|
||
"做一件事",
|
||
"",
|
||
"## 本轮",
|
||
"",
|
||
"方案进度",
|
||
"",
|
||
"最后一轮用户输入",
|
||
"再改一下",
|
||
].join("\n");
|
||
|
||
describe("outlineRequest", () => {
|
||
it("按标题、对话轮次和最后一轮输入切开,不留下正文", () => {
|
||
const segments = outlineRequest([
|
||
{ role: "system", content: sample.slice(0, sample.indexOf("## 更早的对话")) },
|
||
{ role: "user", content: sample.slice(sample.indexOf("## 更早的对话")) },
|
||
]);
|
||
expect(segments.map((item) => item.tag)).toEqual([
|
||
"规则",
|
||
"写入规则",
|
||
"更早的对话",
|
||
"更早·用户",
|
||
"更早·助手",
|
||
"当前产物",
|
||
"设计.氛围",
|
||
"设计.角色",
|
||
"本轮注入",
|
||
"topology",
|
||
"task",
|
||
"本轮",
|
||
"最后一轮用户输入",
|
||
]);
|
||
expect(JSON.stringify(segments)).not.toContain("再改一下");
|
||
expect(JSON.stringify(segments)).not.toContain("雨");
|
||
const again = outlineRequest([
|
||
{ role: "system", content: sample.slice(0, sample.indexOf("## 更早的对话")) },
|
||
{ role: "user", content: sample.slice(sample.indexOf("## 更早的对话")) },
|
||
]);
|
||
expect(again.map((item) => item.hash)).toEqual(segments.map((item) => item.hash));
|
||
});
|
||
|
||
it("工具定义放在正文之前", () => {
|
||
const segments = outlineRequest(
|
||
[{ role: "user", content: "你好" }],
|
||
[
|
||
{
|
||
type: "function",
|
||
function: { name: "insert", description: "写", parameters: {} },
|
||
},
|
||
],
|
||
);
|
||
expect(segments.map((item) => item.tag)).toEqual(["工具", "用户"]);
|
||
});
|
||
});
|
||
|
||
describe("firstCacheBreak", () => {
|
||
it("标出第一处标签或哈希不同的位置", () => {
|
||
const prev = outlineRequest([{ role: "user", content: sample.slice(sample.indexOf("## 更早的对话")) }]);
|
||
const changed = sample.replace("雨", "雪");
|
||
const next = outlineRequest([{ role: "user", content: changed.slice(changed.indexOf("## 更早的对话")) }]);
|
||
const index = firstCacheBreak(prev, next);
|
||
expect(next[index]?.tag).toBe("设计.氛围");
|
||
expect(firstCacheBreak(prev, prev)).toBe(-1);
|
||
expect(firstCacheBreak(prev, prev.slice(0, 3))).toBe(-1);
|
||
expect(firstCacheBreak(prev.slice(0, 3), prev)).toBe(3);
|
||
});
|
||
});
|
||
|
||
describe("annotateCacheBreaks", () => {
|
||
it("按同会话时间比较,窗口里第一条算冷启动", () => {
|
||
const a = outlineRequest([{ role: "user", content: "第一" }]);
|
||
const b = outlineRequest([{ role: "user", content: "第二" }]);
|
||
const marks = annotateCacheBreaks([
|
||
{ id: "2", at: "2026-10-02T02:00:00.000Z", sessionId: "s", segments: b },
|
||
{ id: "1", at: "2026-10-02T01:00:00.000Z", sessionId: "s", segments: a },
|
||
{ id: "9", at: "2026-10-02T03:00:00.000Z", sessionId: "other", segments: a },
|
||
]);
|
||
expect(marks.get("1")).toMatchObject({ cold: true, matched: 0 });
|
||
expect(marks.get("2")).toMatchObject({ cold: false, breakAt: 0, tag: "用户", matched: 0 });
|
||
expect(marks.get("9")?.cold).toBe(true);
|
||
});
|
||
});
|