zhitalk 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent.js +7 -6
- package/dist/agent/context.d.ts +7 -0
- package/dist/agent/context.js +43 -1
- package/dist/agent/db.d.ts +1 -0
- package/dist/agent/db.js +24 -0
- package/dist/agent/model.js +2 -1
- package/dist/agent/prompt.d.ts +2 -1
- package/dist/agent/prompt.js +24 -10
- package/dist/agent/tools/memory_create_tool.js +2 -0
- package/dist/agent/tools/memory_delete_tool.js +2 -0
- package/package.json +1 -1
package/dist/agent/agent.js
CHANGED
|
@@ -4,8 +4,8 @@ import { StateGraph, Annotation, START, END, interrupt, Command, GraphRecursionE
|
|
|
4
4
|
import { messagesStateReducer } from '@langchain/langgraph';
|
|
5
5
|
import { DB_PATH } from './db.js';
|
|
6
6
|
import { tools, maybePersistedOutput, initTools } from './tools.js';
|
|
7
|
-
import { compressMessages } from './context.js';
|
|
8
|
-
import {
|
|
7
|
+
import { compressMessages, findSafeCompressionIndex } from './context.js';
|
|
8
|
+
import { buildSystemPrompt } from './prompt.js';
|
|
9
9
|
import { formatToolLog, formatTodoList } from './colors.js';
|
|
10
10
|
import { processTodoCalls, formatTodoListForPrompt } from './todo.js';
|
|
11
11
|
import { checkReadPermission } from './permission/read.js';
|
|
@@ -126,7 +126,7 @@ function createAgentGraph(toolList) {
|
|
|
126
126
|
}
|
|
127
127
|
return true;
|
|
128
128
|
});
|
|
129
|
-
const messages = [new SystemMessage(
|
|
129
|
+
const messages = [new SystemMessage(buildSystemPrompt())];
|
|
130
130
|
if (state.todoList && state.todoList.length > 0) {
|
|
131
131
|
messages.push(new SystemMessage(`当前任务进度:\n${formatTodoListForPrompt(state.todoList)}`));
|
|
132
132
|
}
|
|
@@ -399,14 +399,15 @@ export async function compressContext(threadId) {
|
|
|
399
399
|
const lastIndex = currentState.values.lastCompressedIndex || 0;
|
|
400
400
|
const count = currentState.values.compressionCount || 0;
|
|
401
401
|
const recentKeep = 6;
|
|
402
|
-
|
|
402
|
+
const safeIndex = findSafeCompressionIndex(messages, recentKeep);
|
|
403
|
+
if (safeIndex <= lastIndex) {
|
|
403
404
|
return { didCompress: false, count };
|
|
404
405
|
}
|
|
405
|
-
const toCompress = messages.slice(lastIndex,
|
|
406
|
+
const toCompress = messages.slice(lastIndex, safeIndex);
|
|
406
407
|
const newSummary = await compressMessages(toCompress, existingSummary);
|
|
407
408
|
await getAgent().updateState(config, {
|
|
408
409
|
contextSummary: newSummary,
|
|
409
|
-
lastCompressedIndex:
|
|
410
|
+
lastCompressedIndex: safeIndex,
|
|
410
411
|
compressionCount: count + 1,
|
|
411
412
|
});
|
|
412
413
|
return { didCompress: true, count: count + 1 };
|
package/dist/agent/context.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
import { BaseMessage } from '@langchain/core/messages';
|
|
2
2
|
export declare function getModelContextLimit(): number;
|
|
3
3
|
export declare function compressMessages(messages: BaseMessage[], existingSummary: string | null): Promise<string>;
|
|
4
|
+
/**
|
|
5
|
+
* 计算安全的压缩边界索引,确保保留区域内的 tool_calls 和 ToolMessage 配对完整。
|
|
6
|
+
*
|
|
7
|
+
* 策略:从 messages.length - minKeep 开始,如果边界切在了 ToolMessage 或其对应的
|
|
8
|
+
* AIMessage 之前,就把边界往前移动,直到整个工具调用单元都被保留。
|
|
9
|
+
*/
|
|
10
|
+
export declare function findSafeCompressionIndex(messages: BaseMessage[], minKeep: number): number;
|
package/dist/agent/context.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HumanMessage, } from '@langchain/core/messages';
|
|
1
|
+
import { HumanMessage, AIMessage, } from '@langchain/core/messages';
|
|
2
2
|
import { createModel } from './model.js';
|
|
3
3
|
import { getModelConfig } from './config.js';
|
|
4
4
|
const MODEL_CONTEXT_LIMITS = {
|
|
@@ -107,3 +107,45 @@ ${text}`;
|
|
|
107
107
|
}
|
|
108
108
|
return newSummary;
|
|
109
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* 计算安全的压缩边界索引,确保保留区域内的 tool_calls 和 ToolMessage 配对完整。
|
|
112
|
+
*
|
|
113
|
+
* 策略:从 messages.length - minKeep 开始,如果边界切在了 ToolMessage 或其对应的
|
|
114
|
+
* AIMessage 之前,就把边界往前移动,直到整个工具调用单元都被保留。
|
|
115
|
+
*/
|
|
116
|
+
export function findSafeCompressionIndex(messages, minKeep) {
|
|
117
|
+
let index = Math.max(0, messages.length - minKeep);
|
|
118
|
+
// 收集当前保留区域内所有 ToolMessage 的 tool_call_id
|
|
119
|
+
const toolCallIdsInKeepRegion = new Set();
|
|
120
|
+
for (let i = index; i < messages.length; i++) {
|
|
121
|
+
if (messages[i].type === 'tool') {
|
|
122
|
+
toolCallIdsInKeepRegion.add(messages[i].tool_call_id);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// 往前移动 index,直到所有 toolCallIdsInKeepRegion 都能在保留区域内找到对应的 AIMessage
|
|
126
|
+
while (index > 0) {
|
|
127
|
+
const prevMsg = messages[index - 1];
|
|
128
|
+
// 如果前一条是 ToolMessage,它也会被纳入保留区,所以加入待匹配集合
|
|
129
|
+
if (prevMsg.type === 'tool') {
|
|
130
|
+
toolCallIdsInKeepRegion.add(prevMsg.tool_call_id);
|
|
131
|
+
index--;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
// 如果前一条是 AIMessage 且有 tool_calls,检查它是否匹配保留区内的 ToolMessage
|
|
135
|
+
if (AIMessage.isInstance(prevMsg) && prevMsg.tool_calls?.length) {
|
|
136
|
+
const hasMatchingCall = prevMsg.tool_calls.some((call) => call.id && toolCallIdsInKeepRegion.has(call.id));
|
|
137
|
+
if (hasMatchingCall) {
|
|
138
|
+
// 这个 AIMessage 必须保留,index 前移
|
|
139
|
+
index--;
|
|
140
|
+
// 这个 AIMessage 可能还有其他 tool_calls,它们对应的 ToolMessage 也必须在保留区
|
|
141
|
+
for (const call of prevMsg.tool_calls) {
|
|
142
|
+
if (call.id)
|
|
143
|
+
toolCallIdsInKeepRegion.add(call.id);
|
|
144
|
+
}
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
return index;
|
|
151
|
+
}
|
package/dist/agent/db.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface MemorySearchResult {
|
|
|
16
16
|
final_score: number;
|
|
17
17
|
}
|
|
18
18
|
export declare function searchMemories(query: string[], limit?: number): MemorySearchResult[];
|
|
19
|
+
export declare function listRecentMemories(limit?: number): MemorySearchResult[];
|
|
19
20
|
export declare function threadIdExists(threadId: string): boolean;
|
|
20
21
|
export declare function initDb(): void;
|
|
21
22
|
export declare function listRecentSessions(): SessionRow[];
|
package/dist/agent/db.js
CHANGED
|
@@ -55,6 +55,30 @@ export function searchMemories(query, limit = 10) {
|
|
|
55
55
|
db.close();
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
export function listRecentMemories(limit = 10) {
|
|
59
|
+
const db = new Database(DB_PATH);
|
|
60
|
+
try {
|
|
61
|
+
const rows = db.prepare(`
|
|
62
|
+
SELECT * FROM memory
|
|
63
|
+
ORDER BY updated_at DESC
|
|
64
|
+
LIMIT ?
|
|
65
|
+
`).all(limit);
|
|
66
|
+
return rows.map((r) => ({
|
|
67
|
+
id: r.id,
|
|
68
|
+
type: r.type,
|
|
69
|
+
content: r.content,
|
|
70
|
+
keywords: r.keywords,
|
|
71
|
+
importance: r.importance,
|
|
72
|
+
session_id: r.session_id,
|
|
73
|
+
created_at: r.created_at,
|
|
74
|
+
updated_at: r.updated_at,
|
|
75
|
+
final_score: 0,
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
db.close();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
58
82
|
export function threadIdExists(threadId) {
|
|
59
83
|
const db = new Database(DB_PATH);
|
|
60
84
|
try {
|
package/dist/agent/model.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ChatOpenAI } from '@langchain/openai';
|
|
2
2
|
import { getModelConfig } from './config.js';
|
|
3
|
-
const modelConfig = getModelConfig();
|
|
4
3
|
export function createModel(options) {
|
|
4
|
+
const modelConfig = getModelConfig();
|
|
5
5
|
const modelKwargs = {};
|
|
6
6
|
if (modelConfig.model.startsWith('kimi') ||
|
|
7
7
|
modelConfig.model.startsWith('minimax')) {
|
|
@@ -20,6 +20,7 @@ export function createModel(options) {
|
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
export async function checkModel() {
|
|
23
|
+
const modelConfig = getModelConfig();
|
|
23
24
|
if (modelConfig.apiKey.length < 20) {
|
|
24
25
|
try {
|
|
25
26
|
const model = createModel({ streaming: false });
|
package/dist/agent/prompt.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export declare
|
|
1
|
+
export declare function invalidateMemoryCache(): void;
|
|
2
|
+
export declare function buildSystemPrompt(): string;
|
package/dist/agent/prompt.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { discoverSkills, getSkillsListText } from './skills.js';
|
|
4
4
|
import { WORKSPACE_DIR } from './config.js';
|
|
5
|
+
import { listRecentMemories } from './db.js';
|
|
5
6
|
// ── Skills ────────────────────────────────────────────────
|
|
6
7
|
discoverSkills();
|
|
7
8
|
const skillsText = getSkillsListText();
|
|
@@ -56,13 +57,26 @@ When a user's request is a complex, multi-step task (such as data analysis, writ
|
|
|
56
57
|
4. Only provide the final answer after all steps are done
|
|
57
58
|
|
|
58
59
|
For simple, single-step tasks, do NOT use the todo list tools.`;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.
|
|
68
|
-
|
|
60
|
+
let cachedMemories = null;
|
|
61
|
+
export function invalidateMemoryCache() {
|
|
62
|
+
cachedMemories = null;
|
|
63
|
+
}
|
|
64
|
+
export function buildSystemPrompt() {
|
|
65
|
+
if (!cachedMemories) {
|
|
66
|
+
cachedMemories = listRecentMemories(10);
|
|
67
|
+
}
|
|
68
|
+
const memorySection = cachedMemories.length > 0
|
|
69
|
+
? `## Recent Memories\n\n${cachedMemories.map((m) => `- ${m.content}`).join('\n')}`
|
|
70
|
+
: '';
|
|
71
|
+
return [
|
|
72
|
+
basePrompt,
|
|
73
|
+
profilePrompt,
|
|
74
|
+
memorySection,
|
|
75
|
+
memoryPrompt,
|
|
76
|
+
skillPrompt,
|
|
77
|
+
dateTimePrompt,
|
|
78
|
+
todoPrompt,
|
|
79
|
+
]
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.join('\n\n');
|
|
82
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import { DB_PATH } from '../db.js';
|
|
3
|
+
import { invalidateMemoryCache } from '../prompt.js';
|
|
3
4
|
const VALID_TYPES = ['fact', 'event', 'preference', 'skill'];
|
|
4
5
|
export async function memoryCreateTool({ type, content, keywords, importance, }, config) {
|
|
5
6
|
if (!VALID_TYPES.includes(type)) {
|
|
@@ -20,6 +21,7 @@ export async function memoryCreateTool({ type, content, keywords, importance, },
|
|
|
20
21
|
const stmt = db.prepare(`INSERT INTO memory (type, content, keywords, importance, session_id)
|
|
21
22
|
VALUES (?, ?, ?, ?, ?)`);
|
|
22
23
|
stmt.run(type, trimmed, keywordsJson, importanceValue, sessionId);
|
|
24
|
+
invalidateMemoryCache();
|
|
23
25
|
return 'Memory saved successfully.';
|
|
24
26
|
}
|
|
25
27
|
catch (err) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import { DB_PATH } from '../db.js';
|
|
3
|
+
import { invalidateMemoryCache } from '../prompt.js';
|
|
3
4
|
export async function memoryDeleteTool({ id, }, _config) {
|
|
4
5
|
if (!Number.isFinite(id) || id <= 0) {
|
|
5
6
|
return 'Error: id must be a positive integer.';
|
|
@@ -11,6 +12,7 @@ export async function memoryDeleteTool({ id, }, _config) {
|
|
|
11
12
|
return `Error: no memory found with id ${id}.`;
|
|
12
13
|
}
|
|
13
14
|
db.prepare('DELETE FROM memory WHERE id = ?').run(id);
|
|
15
|
+
invalidateMemoryCache();
|
|
14
16
|
return `Memory ${id} deleted successfully.`;
|
|
15
17
|
}
|
|
16
18
|
catch (err) {
|
package/package.json
CHANGED