Skip to content

Commit e263fc1

Browse files
author
linyuan.yang
committed
静态 动态 prompt
1 parent 1cb9196 commit e263fc1

7 files changed

Lines changed: 46 additions & 15 deletions

File tree

packages/channel.xiaoai/tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
0 Bytes
Binary file not shown.
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
---
22
vars:
3-
currentTime: 当前时间 (如 2026/1/1 12:00:00)
43
extraInfo: 渠道注入的额外信息 (XML 片段,可为空)
54
---
6-
<current-time>{currentTime}</current-time>
75
{extraInfo}

packages/sbot/src/Agent/AgentRunner.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ export class AgentRunner {
6363
if (!threadId.trim()) throw new Error("threadId not specified");
6464

6565
const signal = sessionManager.getOrCreate(threadId).signal;
66-
const now = new Date();
6766
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
6867
const assetsDir = config.getConfigPath('assets', true);
6968
const httpUrl = config.getHttpUrl();
@@ -83,10 +82,7 @@ export class AgentRunner {
8382

8483
/** 动态 system prompts(每次请求变化,不可缓存) */
8584
const dynamicPrompts: string[] = [
86-
loadPrompt('system/dynamic_context.txt', {
87-
currentTime: now.toLocaleString(undefined, { timeZone: timezone, hour12: false }),
88-
extraInfo,
89-
}),
85+
...(extraInfo?.trim() ? [loadPrompt('system/dynamic_context.txt', { extraInfo })] : []),
9086
];
9187

9288
const container = new ServiceContainer();

packages/sbot/src/Agent/GlobalAgentToolService.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export enum BuiltinProvider {
88
WebFetch = 'builtin_webfetch',
99
Archive = 'builtin_archive',
1010
Sleep = 'builtin_sleep',
11+
Time = 'builtin_time',
1112

1213
Playwright = 'builtin_playwright',
1314
ChromeDevTools = 'builtin_chrome-devtools-mcp',
@@ -39,6 +40,10 @@ export function initGlobalAgentToolService() {
3940
const { createSleepTool } = await import("../Tools/Sleep/index.js");
4041
return [createSleepTool()];
4142
}, '等待/暂停执行');
43+
globalAgentToolService.registerToolFactory(BuiltinProvider.Time, async () => {
44+
const { createTimeTool } = await import("../Tools/Time/index.js");
45+
return [createTimeTool()];
46+
}, '获取当前时间');
4247
globalAgentToolService.registerMcpServers({
4348
[BuiltinProvider.Playwright]: {
4449
"command": "npx",
@@ -78,6 +83,7 @@ export async function refreshBuiltinTools() {
7883
BuiltinProvider.WebFetch,
7984
BuiltinProvider.Archive,
8085
BuiltinProvider.Sleep,
86+
BuiltinProvider.Time,
8187
// BuiltinProvider.GameData,
8288
);
8389
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { DynamicStructuredTool, type StructuredToolInterface } from '@langchain/core/tools';
2+
import { z } from 'zod';
3+
import { createTextContent, createSuccessResult, type MCPToolResult } from 'scorpio.ai';
4+
5+
export const TIME_TOOL_NAME = 'get_current_time' as const;
6+
7+
export function createTimeTool(): StructuredToolInterface {
8+
return new DynamicStructuredTool({
9+
name: TIME_TOOL_NAME,
10+
description: 'Get the current date and time with timezone information.',
11+
schema: z.object({}) as any,
12+
func: async (): Promise<MCPToolResult> => {
13+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
14+
const now = new Date().toLocaleString(undefined, { timeZone: timezone, hour12: false });
15+
return createSuccessResult(createTextContent(`${now} (${timezone})`));
16+
},
17+
});
18+
}

packages/scorpio.ai/src/Model/AnthropicModelService.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,18 @@ export class AnthropicModelService implements IModelService {
4343
const input = typeof prompt === 'string' ? prompt : this.applyCache(toBaseMessages(prompt));
4444
const result = await m.invoke(input, {
4545
...(options?.signal && { signal: options.signal }),
46-
...(this.cacheControl && { cache_control: this.cacheControl }),
4746
});
4847
return toChatMessage(result);
4948
}
5049

5150
bindTools(tools: any[]): void {
52-
this.boundModel = this.model!.bindTools(tools);
51+
if (this.cacheControl && tools.length > 0) {
52+
const formatted = (this.model! as any).formatStructuredToolToAnthropic(tools);
53+
formatted[formatted.length - 1].cache_control = this.cacheControl;
54+
this.boundModel = (this.model! as any).withConfig({ tools: formatted });
55+
} else {
56+
this.boundModel = this.model!.bindTools(tools);
57+
}
5358
}
5459

5560
async invokeStructured<T = any>(schema: any, prompt: string | ChatMessage[], options?: { signal?: AbortSignal }): Promise<T> {
@@ -62,7 +67,6 @@ export class AnthropicModelService implements IModelService {
6267
const input = typeof messages === 'string' ? messages : this.applyCache(toBaseMessages(messages));
6368
const lcStream = await m.stream(input, {
6469
...(options?.signal && { signal: options.signal }),
65-
...(this.cacheControl && { cache_control: this.cacheControl }),
6670
});
6771
return (async function* () {
6872
let accumulated: AIMessageChunk | undefined;
@@ -80,24 +84,33 @@ export class AnthropicModelService implements IModelService {
8084
}
8185

8286
private applyCache(messages: BaseMessage[]): BaseMessage[] {
83-
if (!this.cacheControl) return messages;
87+
if (!this.cacheControl || messages.length === 0) return messages;
8488

89+
// breakpoint 1: system message (last block) — covers static + dynamic as a whole
90+
// system is built once per stream() call, so within a ReAct loop it's always identical
8591
for (const msg of messages) {
8692
if (msg instanceof SystemMessage) {
87-
this.addCacheMarker(msg);
93+
this.addCacheMarker(msg, 'last');
8894
break;
8995
}
9096
}
9197

98+
// breakpoint 2: conversation history tail — next call reuses the entire prefix
99+
const last = messages[messages.length - 1];
100+
if (!(last instanceof SystemMessage)) {
101+
this.addCacheMarker(last, 'first');
102+
}
103+
92104
return messages;
93105
}
94106

95-
private addCacheMarker(message: BaseMessage): void {
107+
private addCacheMarker(message: BaseMessage, position: 'first' | 'last' = 'first'): void {
96108
const content = message.content;
97109
if (typeof content === 'string') {
98110
message.content = [{ type: "text", text: content, cache_control: this.cacheControl }];
99111
} else if (Array.isArray(content) && content.length > 0) {
100-
content[0] = { ...content[0], cache_control: this.cacheControl };
112+
const idx = position === 'last' ? content.length - 1 : 0;
113+
content[idx] = { ...content[idx], cache_control: this.cacheControl };
101114
}
102115
}
103116
}

0 commit comments

Comments
 (0)