统一在预设包装层做提示词夹心,并修正探针实际发信路径。
让所有模型请求与界面预览共用同一套夹心,避免探针与运行时行为分叉。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -303,7 +303,7 @@ Runtime Session
|
|||||||
|
|
||||||
因此,同一个 preset 可以用于多个创作流程;同一个创作流程也可以切换不同 preset。二者是正交关系。
|
因此,同一个 preset 可以用于多个创作流程;同一个创作流程也可以切换不同 preset。二者是正交关系。
|
||||||
|
|
||||||
生成参数可全局合并进 LLM 请求。**prompt 条目 + marker 填洞只在 play worker 上用**:创作步 / 总管 tool loop 不得把角色卡骨架套到 design-step 上,也不得在填洞后再叠一份相同的 user 上下文。
|
生成参数与 **prompt 条目 + marker 填洞** 均在 `wrapLlmForSession`(`PresetLlmProvider`)统一做:凡 LLM 请求先夹心再发。调用方只交任务 messages;任务 system / 创作上下文进 `worldBookBefore` 等洞,**禁止**在 `prompt_order` 之外再前置一条 system。无选用预设时原样转发。
|
||||||
|
|
||||||
## 6. 第一版导入策略
|
## 6. 第一版导入策略
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { loadAppSettings } from "../config/settings.js";
|
import { loadAppSettings } from "../config/settings.js";
|
||||||
|
import { frameMessagesWithPreset } from "../preset/play-frame.js";
|
||||||
import { resolveActivePreset } from "../preset/store.js";
|
import { resolveActivePreset } from "../preset/store.js";
|
||||||
import type { PresetPackage } from "../types/preset.js";
|
import type { PresetPackage } from "../types/preset.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -23,7 +24,7 @@ function readActivePreset(): PresetPackage | null {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 统计在内、预设在外:痕迹里的 messages / generation 就是发给模型的那一份。
|
* 统计在内、预设在外:痕迹里的 messages / generation 就是发给模型的那一份。
|
||||||
* 当前没有选用预设时仍包一层,请求原样转发。
|
* 有选用预设时:在此按 prompt_order 夹心包裹,并合并 generation。
|
||||||
*/
|
*/
|
||||||
export function wrapLlmForSession(
|
export function wrapLlmForSession(
|
||||||
inner: LlmProvider,
|
inner: LlmProvider,
|
||||||
@@ -36,10 +37,7 @@ export function wrapLlmForSession(
|
|||||||
return new PresetLlmProvider(tracked, getPreset);
|
return new PresetLlmProvider(tracked, getPreset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 所有 LLM 请求:preset 夹心 + 生成参数。无选用预设则原样转发。 */
|
||||||
* 在所有 LLM 请求上合并当前 preset 的生成参数。
|
|
||||||
* prompt / marker 不在此前置,由 play worker 按扩展 prompt_order 填洞。
|
|
||||||
*/
|
|
||||||
export class PresetLlmProvider implements LlmProvider {
|
export class PresetLlmProvider implements LlmProvider {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly inner: LlmProvider,
|
private readonly inner: LlmProvider,
|
||||||
@@ -52,9 +50,8 @@ export class PresetLlmProvider implements LlmProvider {
|
|||||||
): { messages: ChatMessage[]; options: CompleteOptions | undefined } {
|
): { messages: ChatMessage[]; options: CompleteOptions | undefined } {
|
||||||
const preset = this.getPreset();
|
const preset = this.getPreset();
|
||||||
if (!preset) return { messages, options };
|
if (!preset) return { messages, options };
|
||||||
// 生成参数全局合并;prompt/marker 只在 play worker 按 prompt_order 填洞,这里不再前置。
|
|
||||||
return {
|
return {
|
||||||
messages,
|
messages: frameMessagesWithPreset(preset, messages),
|
||||||
options: {
|
options: {
|
||||||
...options,
|
...options,
|
||||||
generation: options?.generation ?? preset.generation,
|
generation: options?.generation ?? preset.generation,
|
||||||
@@ -97,6 +94,8 @@ export class PresetLlmProvider implements LlmProvider {
|
|||||||
return this.inner.completeWithTools(prepared.messages, {
|
return this.inner.completeWithTools(prepared.messages, {
|
||||||
...options,
|
...options,
|
||||||
...prepared.options,
|
...prepared.options,
|
||||||
|
tools: options.tools,
|
||||||
|
toolChoice: options.toolChoice,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +110,12 @@ export class PresetLlmProvider implements LlmProvider {
|
|||||||
const prepared = this.prepare(messages, options);
|
const prepared = this.prepare(messages, options);
|
||||||
return this.inner.completeWithToolsStream(
|
return this.inner.completeWithToolsStream(
|
||||||
prepared.messages,
|
prepared.messages,
|
||||||
{ ...options, ...prepared.options },
|
{
|
||||||
|
...options,
|
||||||
|
...prepared.options,
|
||||||
|
tools: options.tools,
|
||||||
|
toolChoice: options.toolChoice,
|
||||||
|
},
|
||||||
callbacks,
|
callbacks,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,32 @@ import { assemblePresetMessages } from "./assembler.js";
|
|||||||
import { applyExtendedMarkers } from "./markers.js";
|
import { applyExtendedMarkers } from "./markers.js";
|
||||||
import {
|
import {
|
||||||
resolveWorldInfoMarker,
|
resolveWorldInfoMarker,
|
||||||
|
type WorldInfoEntry,
|
||||||
type WorldInfoPack,
|
type WorldInfoPack,
|
||||||
} from "./world-info-pack.js";
|
} from "./world-info-pack.js";
|
||||||
|
|
||||||
|
const TASK_SYSTEM_ENTRY_ID = "task-system";
|
||||||
|
const TASK_SYSTEM_ENTRY_NAME = "任务契约";
|
||||||
|
|
||||||
|
function withTaskSystem(
|
||||||
|
pack: WorldInfoPack,
|
||||||
|
systemPrompt: string,
|
||||||
|
): WorldInfoPack {
|
||||||
|
const text = systemPrompt.trim();
|
||||||
|
if (!text) return pack;
|
||||||
|
const entry: WorldInfoEntry = {
|
||||||
|
id: TASK_SYSTEM_ENTRY_ID,
|
||||||
|
name: TASK_SYSTEM_ENTRY_NAME,
|
||||||
|
content: text,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...pack,
|
||||||
|
worldBookBefore: [entry, ...pack.worldBookBefore],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play worker:system 契约 + 按扩展 prompt_order 填洞。
|
* 严格按扩展 prompt_order 填洞。任务契约进 worldBookBefore,不在预设外另挂 system。
|
||||||
* 不要再把同一份 context-order 正文叠成第二包 user。
|
|
||||||
*/
|
*/
|
||||||
export function assemblePlayWorkerMessages(params: {
|
export function assemblePlayWorkerMessages(params: {
|
||||||
systemPrompt: string;
|
systemPrompt: string;
|
||||||
@@ -17,8 +37,82 @@ export function assemblePlayWorkerMessages(params: {
|
|||||||
pack: WorldInfoPack;
|
pack: WorldInfoPack;
|
||||||
}): ChatMessage[] {
|
}): ChatMessage[] {
|
||||||
const framed = applyExtendedMarkers(params.preset);
|
const framed = applyExtendedMarkers(params.preset);
|
||||||
const filled = assemblePresetMessages(framed, (id) =>
|
const pack = withTaskSystem(params.pack, params.systemPrompt);
|
||||||
resolveWorldInfoMarker(params.pack, id),
|
return assemblePresetMessages(framed, (id) =>
|
||||||
|
resolveWorldInfoMarker(pack, id),
|
||||||
);
|
);
|
||||||
return [{ role: "system", content: params.systemPrompt }, ...filled];
|
}
|
||||||
|
|
||||||
|
function isToolLoopBoundary(m: ChatMessage): boolean {
|
||||||
|
if (m.role === "tool") return true;
|
||||||
|
return Boolean(
|
||||||
|
m.role === "assistant" && m.tool_calls && m.tool_calls.length > 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatHistoryMessages(msgs: ChatMessage[]): string {
|
||||||
|
const parts: string[] = [];
|
||||||
|
for (const m of msgs) {
|
||||||
|
if (m.role === "user" && m.content.trim()) {
|
||||||
|
parts.push(`【用户】\n${m.content.trim()}`);
|
||||||
|
} else if (m.role === "assistant") {
|
||||||
|
const text = (m.content ?? "").trim();
|
||||||
|
if (text) parts.push(`【助手】\n${text}`);
|
||||||
|
} else if (m.role === "system" && m.content.trim()) {
|
||||||
|
parts.push(`【系统】\n${m.content.trim()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parts.join("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务 messages → 只填四袋 + chatHistory;发出序列的首尾由预设 prompt_order 决定。
|
||||||
|
* tool 循环尾接在夹心后。
|
||||||
|
*/
|
||||||
|
export function frameMessagesWithPreset(
|
||||||
|
preset: PresetPackage,
|
||||||
|
messages: ChatMessage[],
|
||||||
|
): ChatMessage[] {
|
||||||
|
let splitAt = messages.length;
|
||||||
|
for (let i = 0; i < messages.length; i++) {
|
||||||
|
if (isToolLoopBoundary(messages[i]!)) {
|
||||||
|
splitAt = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const prefix = messages.slice(0, splitAt);
|
||||||
|
const continuation = messages.slice(splitAt);
|
||||||
|
|
||||||
|
const systems: string[] = [];
|
||||||
|
const body: ChatMessage[] = [];
|
||||||
|
for (const m of prefix) {
|
||||||
|
if (m.role === "system") systems.push(m.content);
|
||||||
|
else body.push(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
let turn = "";
|
||||||
|
let history = "";
|
||||||
|
if (body.length > 0) {
|
||||||
|
const last = body[body.length - 1]!;
|
||||||
|
if (last.role === "user") {
|
||||||
|
turn = last.content.trim();
|
||||||
|
history = formatHistoryMessages(body.slice(0, -1));
|
||||||
|
} else {
|
||||||
|
history = formatHistoryMessages(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const framed = assemblePlayWorkerMessages({
|
||||||
|
systemPrompt: systems.join("\n\n"),
|
||||||
|
preset,
|
||||||
|
pack: {
|
||||||
|
worldBookBefore: [],
|
||||||
|
history,
|
||||||
|
worldBookAfter: [],
|
||||||
|
turn,
|
||||||
|
postTurn: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return continuation.length > 0 ? [...framed, ...continuation] : framed;
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-6
@@ -62,6 +62,7 @@ export function worldInfoPackFromProbeInput(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 已夹心的试跑 messages(仅 UI / 对照);真正发模型走任务消息 + PresetLlmProvider。 */
|
||||||
export function assemblePresetProbe(
|
export function assemblePresetProbe(
|
||||||
preset: PresetPackage,
|
preset: PresetPackage,
|
||||||
input: PresetProbeInput,
|
input: PresetProbeInput,
|
||||||
@@ -73,6 +74,32 @@ export function assemblePresetProbe(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 交给 wrapLlmForSession 的任务消息(未夹心)。 */
|
||||||
|
export function buildPresetProbeTaskMessages(
|
||||||
|
input: PresetProbeInput,
|
||||||
|
): ChatMessage[] {
|
||||||
|
const pack = worldInfoPackFromProbeInput(input);
|
||||||
|
const parts: string[] = [];
|
||||||
|
for (const e of pack.worldBookBefore) {
|
||||||
|
parts.push(`### ${e.name}\n\n${e.content}`);
|
||||||
|
}
|
||||||
|
if (pack.history.trim()) parts.push(pack.history.trim());
|
||||||
|
for (const e of pack.worldBookAfter) {
|
||||||
|
parts.push(`### ${e.name}\n\n${e.content}`);
|
||||||
|
}
|
||||||
|
for (const e of pack.postTurn) {
|
||||||
|
parts.push(`### ${e.name}\n\n${e.content}`);
|
||||||
|
}
|
||||||
|
const ctx = parts.join("\n\n").trim();
|
||||||
|
const user = ctx
|
||||||
|
? `${ctx}\n\n## 本轮输入\n${pack.turn}`
|
||||||
|
: pack.turn;
|
||||||
|
return [
|
||||||
|
{ role: "system", content: PROBE_SYSTEM_PROMPT },
|
||||||
|
{ role: "user", content: user },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
export function serializeProbeMessages(
|
export function serializeProbeMessages(
|
||||||
messages: ChatMessage[],
|
messages: ChatMessage[],
|
||||||
): PresetProbeMessage[] {
|
): PresetProbeMessage[] {
|
||||||
@@ -94,11 +121,11 @@ export async function runPresetProbe(params: {
|
|||||||
throw new Error("缺少本轮输入");
|
throw new Error("缺少本轮输入");
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = assemblePresetProbe(params.preset, {
|
const input = { ...params.input, message };
|
||||||
...params.input,
|
// UI 仍展示「理想分袋夹心」;发模型只交任务消息,由统一包装层夹心
|
||||||
message,
|
const serialized = serializeProbeMessages(
|
||||||
});
|
assemblePresetProbe(params.preset, input),
|
||||||
const serialized = serializeProbeMessages(messages);
|
);
|
||||||
|
|
||||||
if (!params.complete) {
|
if (!params.complete) {
|
||||||
return {
|
return {
|
||||||
@@ -111,7 +138,7 @@ export async function runPresetProbe(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await params.complete(messages);
|
const result = await params.complete(buildPresetProbeTaskMessages(input));
|
||||||
return {
|
return {
|
||||||
messages: serialized,
|
messages: serialized,
|
||||||
reply: result.content,
|
reply: result.content,
|
||||||
|
|||||||
@@ -51,9 +51,11 @@ describe("assemblePlayWorkerMessages", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(messages.map((m) => [m.role, m.content])).toEqual([
|
expect(messages.map((m) => [m.role, m.content])).toEqual([
|
||||||
["system", "SKILL"],
|
|
||||||
["system", "MAIN"],
|
["system", "MAIN"],
|
||||||
["system", "### 舞台\n\n古镇夜雨"],
|
[
|
||||||
|
"system",
|
||||||
|
"### 任务契约\n\nSKILL\n\n### 舞台\n\n古镇夜雨",
|
||||||
|
],
|
||||||
["system", "用户:昨天来过"],
|
["system", "用户:昨天来过"],
|
||||||
["system", "### 变量\n\nHP 3"],
|
["system", "### 变量\n\nHP 3"],
|
||||||
["user", "我推门"],
|
["user", "我推门"],
|
||||||
@@ -62,6 +64,57 @@ describe("assemblePlayWorkerMessages", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not emit any message before the first preset order entry", () => {
|
||||||
|
const report = importSillyTavernPreset({
|
||||||
|
prompts: [
|
||||||
|
{
|
||||||
|
identifier: "wb-head",
|
||||||
|
name: "世界书首",
|
||||||
|
role: "system",
|
||||||
|
content: "<世界书>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
identifier: "chatHistory",
|
||||||
|
name: "History",
|
||||||
|
role: "system",
|
||||||
|
marker: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
identifier: "wb-tail",
|
||||||
|
name: "世界书尾",
|
||||||
|
role: "system",
|
||||||
|
content: "</世界书>",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
prompt_order: [
|
||||||
|
{
|
||||||
|
character_id: 1,
|
||||||
|
order: [
|
||||||
|
{ identifier: "wb-head", enabled: true },
|
||||||
|
{ identifier: "chatHistory", enabled: true },
|
||||||
|
{ identifier: "wb-tail", enabled: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const messages = assemblePlayWorkerMessages({
|
||||||
|
systemPrompt: "创作步骤契约",
|
||||||
|
preset: report.preset,
|
||||||
|
pack: {
|
||||||
|
worldBookBefore: [{ id: "lore", name: "设定", content: "港口" }],
|
||||||
|
history: "昨日对话",
|
||||||
|
worldBookAfter: [],
|
||||||
|
turn: "本轮",
|
||||||
|
postTurn: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(messages[0]).toEqual({ role: "system", content: "<世界书>" });
|
||||||
|
expect(messages.some((m) => m.content === "创作步骤契约")).toBe(false);
|
||||||
|
expect(
|
||||||
|
messages.some((m) => m.content.includes("### 任务契约\n\n创作步骤契约")),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("does not fill ST charDescription even if resolver returns content", () => {
|
it("does not fill ST charDescription even if resolver returns content", () => {
|
||||||
const report = importSillyTavernPreset({
|
const report = importSillyTavernPreset({
|
||||||
prompts: [
|
prompts: [
|
||||||
|
|||||||
@@ -46,9 +46,11 @@ describe("assemblePresetProbe", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(messages.map((m) => [m.role, m.content])).toEqual([
|
expect(messages.map((m) => [m.role, m.content])).toEqual([
|
||||||
["system", PROBE_SYSTEM_PROMPT],
|
|
||||||
["system", "MAIN"],
|
["system", "MAIN"],
|
||||||
["system", `### 试跑设定\n\n${DEFAULT_PROBE_CONTEXT.loreBefore}`],
|
[
|
||||||
|
"system",
|
||||||
|
`### 任务契约\n\n${PROBE_SYSTEM_PROMPT}\n\n### 试跑设定\n\n${DEFAULT_PROBE_CONTEXT.loreBefore}`,
|
||||||
|
],
|
||||||
["system", DEFAULT_PROBE_CONTEXT.history],
|
["system", DEFAULT_PROBE_CONTEXT.history],
|
||||||
["system", `### 本轮状态\n\n${DEFAULT_PROBE_CONTEXT.loreAfter}`],
|
["system", `### 本轮状态\n\n${DEFAULT_PROBE_CONTEXT.loreAfter}`],
|
||||||
["user", "我推开门。"],
|
["user", "我推开门。"],
|
||||||
@@ -66,8 +68,8 @@ describe("assemblePresetProbe", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(messages.map((m) => m.content)).toEqual([
|
expect(messages.map((m) => m.content)).toEqual([
|
||||||
PROBE_SYSTEM_PROMPT,
|
|
||||||
"MAIN",
|
"MAIN",
|
||||||
|
`### 任务契约\n\n${PROBE_SYSTEM_PROMPT}`,
|
||||||
"你好",
|
"你好",
|
||||||
"PHI",
|
"PHI",
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ function recordingProvider(): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("wrapLlmForSession", () => {
|
describe("wrapLlmForSession", () => {
|
||||||
it("context trace matches the exact messages and generation sent to the inner LLM", async () => {
|
it("frames task messages with preset prompt_order and merges generation", async () => {
|
||||||
const { provider, received } = recordingProvider();
|
const { provider, received } = recordingProvider();
|
||||||
const ctx: LlmTrackingContext = {};
|
const ctx: LlmTrackingContext = {};
|
||||||
const llm = wrapLlmForSession(provider, { current: ctx }, () => samplePreset);
|
const llm = wrapLlmForSession(provider, { current: ctx }, () => samplePreset);
|
||||||
@@ -64,14 +64,15 @@ describe("wrapLlmForSession", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(received).toHaveLength(1);
|
expect(received).toHaveLength(1);
|
||||||
expect(received[0].messages).toEqual([{ role: "user", content: "TASK" }]);
|
expect(received[0].messages.map((m) => [m.role, m.content])).toEqual([
|
||||||
|
["system", "PRESET_RULES"],
|
||||||
|
["user", "TASK"],
|
||||||
|
]);
|
||||||
expect(received[0].generation).toMatchObject({
|
expect(received[0].generation).toMatchObject({
|
||||||
temperature: 0.91,
|
temperature: 0.91,
|
||||||
maxOutputTokens: 1234,
|
maxOutputTokens: 1234,
|
||||||
});
|
});
|
||||||
expect(ctx.pendingContextTrace?.messages).toEqual([
|
expect(ctx.pendingContextTrace?.messages).toEqual(received[0].messages);
|
||||||
{ role: "user", content: "TASK" },
|
|
||||||
]);
|
|
||||||
expect(ctx.pendingContextTrace?.generation).toMatchObject({
|
expect(ctx.pendingContextTrace?.generation).toMatchObject({
|
||||||
temperature: 0.91,
|
temperature: 0.91,
|
||||||
maxOutputTokens: 1234,
|
maxOutputTokens: 1234,
|
||||||
@@ -80,7 +81,44 @@ describe("wrapLlmForSession", () => {
|
|||||||
expect(ctx.pendingContextTrace?.model).toBe("rec-model");
|
expect(ctx.pendingContextTrace?.model).toBe("rec-model");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("completeStream also records the merged request", async () => {
|
it("puts task system inside worldBookBefore, not before the preset shell", async () => {
|
||||||
|
const { provider, received } = recordingProvider();
|
||||||
|
const llm = wrapLlmForSession(provider, undefined, () => samplePreset);
|
||||||
|
|
||||||
|
await llm.completeWithTools(
|
||||||
|
[
|
||||||
|
{ role: "system", content: "SKILL" },
|
||||||
|
{ role: "user", content: "do it" },
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: null,
|
||||||
|
tool_calls: [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
type: "function",
|
||||||
|
function: { name: "t", arguments: "{}" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ role: "tool", content: "{}", tool_call_id: "1" },
|
||||||
|
],
|
||||||
|
{ tools: [], caller: "dictate_agent" },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(received[0].messages[0].content).toBe("PRESET_RULES");
|
||||||
|
expect(
|
||||||
|
received[0].messages.some(
|
||||||
|
(m) => m.content === "### 任务契约\n\nSKILL",
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
expect(received[0].messages.some((m) => m.content === "do it")).toBe(true);
|
||||||
|
expect(received[0].messages.map((m) => m.role).slice(-2)).toEqual([
|
||||||
|
"assistant",
|
||||||
|
"tool",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("completeStream also records the framed request", async () => {
|
||||||
const { provider, received } = recordingProvider();
|
const { provider, received } = recordingProvider();
|
||||||
const ctx: LlmTrackingContext = {};
|
const ctx: LlmTrackingContext = {};
|
||||||
const llm = wrapLlmForSession(provider, { current: ctx }, () => samplePreset);
|
const llm = wrapLlmForSession(provider, { current: ctx }, () => samplePreset);
|
||||||
@@ -93,7 +131,9 @@ describe("wrapLlmForSession", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(chunks.join("")).toBe("ok");
|
expect(chunks.join("")).toBe("ok");
|
||||||
expect(received[0].messages).toEqual([{ role: "user", content: "TASK" }]);
|
expect(received[0].messages.some((m) => m.content === "PRESET_RULES")).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
expect(ctx.pendingContextTrace?.messages).toEqual(received[0].messages);
|
expect(ctx.pendingContextTrace?.messages).toEqual(received[0].messages);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user