knight-agent
v1.4.9
Published
Knight-agent智能体架构
Readme
Knight Agent — 架构使用手册
目录
- 1. 架构概览
- 2. 快速开始
- 3. 核心概念
- 4. LLM 平台配置
- 5. 系统提示词
- 6. 工具定义与调用
- 7. 对话请求
- 8. 消息缓存与轮次管理
- 9. 状态导出与还原
- 10. 并发控制
- 11. 工具参考
- 12. 高级用法
- 13. 完整示例
1. 架构概览
┌──────────────────────────────────────────────────┐
│ AbstractLLM │
│ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │
│ │ config │ │ messages │ │ sending │ │
│ │ (Config) │ │(Message) │ │ (lock) │ │
│ └──────────┘ └────┬─────┘ └────────────────┘ │
│ │ │ │
│ │ ┌─────────┴──────────┐ │
│ │ │ MessageData[] │ │
│ │ │ OnTrim/OnCompact │ │
│ │ └────────────────────┘ │
│ │ │
│ ┌────▼──────────────────────────────────────┐ │
│ │ OnSend(prompt, tools?, onChunk?) │ │
│ │ → OnAddUserMessage │ │
│ │ → OnCompactMessages │ │
│ │ → messages.OnTrim │ │
│ │ → OnRequest (HTTP) │ │
│ │ → OnHandleResponse (Tool Call Loop) │ │
│ └────────────────────────────────────────────┘ │
└─────────────────────┬────────────────────────────┘
│
┌──────────────┼──────────────────┐
│ │ │
Claude DeepSeek Gemini OpenAI QWen Ollama核心流程: OnSend(prompt) → 消息预处理 → HTTP 请求 → 响应解析 → 工具调用循环
继承体系: AbstractObject → AbstractLLM<T> → Claude / DeepSeek / Gemini / Ollama / OpenAI / QWen
2. 快速开始
import { AbstractLLM, LLMUtility } from "knight-agent";
// 1. 创建 LLM 实例(系统提示词通过 Config 传入,构造时自动消费)
const llm = AbstractLLM.OnCreat({
type: "DeepSeek",
model: "deepseek-chat",
apiKey: "sk-your-api-key",
stream: true,
systemPrompt: "你是一个有帮助的助手。",
});
// 2. 发送消息
const response = await llm.OnSend("你好,介绍一下你自己");
console.log(response.content);
console.log(response.usage); // { input: 50, output: 30, total: 80 }3. 核心概念
3.1 AbstractLLM(抽象大模型)
框架核心基类,封装了消息管理、请求发送、工具调用循环、配置校验等通用逻辑。各平台通过继承并实现抽象方法接入。
abstract class AbstractLLM<T extends Config> extends AbstractObject {
// ===== 静态成员 =====
static OnCreat(config, data?): AbstractLLM; // 工厂创建
static OnCheckConfig(config): CheckResult; // 配置校验
// ===== 实例成员 =====
protected config: T; // 当前配置
protected readonly messages: Message; // 消息缓存
// 配置
OnSetConfig(config): CheckResult; // 设置/更新配置
OnCheckPlatfrom(config): CheckResult; // 检测平台类型是否变更
// 对话
OnSend(prompt, tools?, onChunk?): Promise<Response>; // 发送消息
// 工具
OnExecuteTool(toolCall, tool): Promise<ToolResult>; // 手动执行工具
// 生命周期
OnImport(data): void; // 导入数据
OnExport(): MessageData[]; // 导出消息列表
OnDestroy(): boolean; // 销毁
// 抽象方法(派生类必须实现)
protected abstract OnBuildBody(
messages: MessageData[],
tools: ToolData[],
stream: boolean
): Record<string, unknown>; // 构建平台 API 请求体
protected abstract OnRequest(
tools: ToolData[],
onChunk?: StreamCallback
): Promise<Response>; // 发起 HTTP 请求
protected abstract OnStopReason(
reason: string
): FinishReason; // 平台停止原因映射
}OnSend 内部流程:
sending锁检查(防重入)- 参数校验(config 为空 / prompt 为空 → 返回错误 Response)
OnAddUserMessage(prompt)— 添加用户消息OnCompactMessages()— 截断过长工具输出(基类实现)messages.OnTrim()— Token + 轮次混合裁剪OnBuildBody(messages, tools, stream)— 构建平台请求体,将统一 MessageData[] 映射为平台 API wire formatOnRequest(tools, onChunk)— 发起 HTTP 请求OnAddAssistantMessage(content, toolCalls, result.usage, result.thinkingContent)— 添加助手消息(usage 由 Message.OnAdd 自动累加,thinkingContent 持久化到 MessageData)OnHandleResponse(...)— 工具调用循环(最多 100 轮)
3.2 Message(消息缓存)
统一管理 LLM 上下文消息,系统消息始终存储在 messages[0](永不裁剪):
class Message {
constructor(maxCache?: number); // 默认 8000 tokens
OnSetSystem(message: MessageData): void; // 设置/替换系统消息
OnGetSystem(): MessageData | undefined; // 获取系统消息
OnAdd(message: MessageData): this; // 添加单条消息
OnAddRange(list: MessageData[]): this; // 批量添加
OnGetAll(): MessageData[]; // 获取全部消息(含系统消息的副本)
OnGetLast(): MessageData[]; // 获取最后一轮消息
OnGetCount(): number; // 消息总数
OnGetRoundCount(): number; // 当前轮次数(role==="user" 的消息数)
OnGetMaxRounds(): number; // 最大轮次容量(内部自动计算 = maxCache / 80)
OnGetRoundPercent(): number; // 轮次利用率 0~1
OnSetMaxCache(maxTokens): void; // 设置 Token 上限(同步更新 maxRounds)
OnGetTokenCount(): number; // Token 估算(基于消息内容字符数)
OnGetTokenPercent(): number; // Token 利用率 0~1
OnGetRoundUsage(): Usage; // 本轮累计 Token 用量(计费)
OnTrim(): number; // 混合裁剪(Token 硬限制 + 轮次软限制)
OnCompact(transform): number; // 逐条转换(工具输出截断等)
OnRestore(data: MessageData[]): void; // 从快照恢复
OnClear(): void; // 清空
}系统消息模型:
- 系统消息始终存储在
messages[0],角色为"system" OnTrim()跳过系统消息(从 index 1 开始),永不裁剪OnGetAll()返回全部消息(含系统消息)- 各平台在
OnBuildBody()中通过OnGetSystem()获取并按需转换:- 数组型(Ollama/OpenAI): 系统消息已在数组中,直接发送
- 注入型(DeepSeek/QWen): 提取后与 JSONSchema prompt 合并,替换回数组首位
- 字段型(Claude/Gemini): 提取后放入请求体的独立字段 (
system/systemInstruction)
4. LLM 平台配置
4.1 通用配置字段
所有平台配置均继承自 LLMUtility.Config:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| type | string | — | 平台标识:"Claude" / "DeepSeek" / "Gemini" / "OpenAI" / "QWen" / "Ollama" |
| url | string | 平台默认 | API 端点 |
| model | string | 平台默认 | 模型名称 |
| apiKey | string | — | API 密钥(必填) |
| timeout | number | 60000 | 请求超时 (ms) |
| temperature | number | 1 | 采样温度 (0~2) |
| topP | number | 1 | 核采样 (0~1) |
| maxTokens | number | 4096 | 最大输出 token 数 |
| stop | string \| string[] | — | 停止序列(数组最多 4 条) |
| stream | boolean | false | 流式响应开关 |
| seed | number | — | 可复现采样种子(须为整数) |
| systemPrompt | string | — | 系统提示词(值变更时自动更新) |
| maxCache | number | 8000 | 最大 Token 缓存上限(超出自动裁剪) |
| maxToolOutput | number | 800 | 工具输出截断长度 |
| responseFormat | string \| Record<string, any> | "text" | 输出格式:字符串为模式名("text"/"json_object"/"json_schema"),对象为结构化输出约束(任意 JSON Schema 结构),各平台自动映射 |
4.2 配置校验
AbstractLLM.OnCheckConfig(config) 提供逐字段细化校验:
const result = AbstractLLM.OnCheckConfig({
type: "DeepSeek",
model: "deepseek-chat",
apiKey: "sk-xxx",
});
// { valid: true }
const bad = AbstractLLM.OnCheckConfig({
type: "",
model: "deepseek-chat",
apiKey: "",
});
// { valid: false, error: "config.type is required" }校验规则:
- 必填字段:
type、model必须非空 type必须已注册到Component命名空间url若提供,须为http://或https://开头- 数值范围:
temperature(0~2)、topP(0~1)、maxTokens/timeout/maxCache/maxToolOutput(> 0) seed须为整数stop数组最多 4 条
4.3 配置热更新
OnSetConfig 可在任意时机调用,配置未变更时零开销返回,配置变更时合并新旧配置(保留平台默认值):
// 首次设置
llm.OnSetConfig({ type: "DeepSeek", model: "deepseek-chat", apiKey: "sk-aaa" });
// 仅更新 apiKey,url/model 等保留原有值
llm.OnSetConfig({ apiKey: "sk-bbb" });
// 相同配置再次调用 — 无操作
llm.OnSetConfig({ type: "DeepSeek", model: "deepseek-chat", apiKey: "sk-bbb" });
// → { valid: true },不做任何更新原理: 配置合并使用
{ ...this.config, ...config }确保平台默认值不丢失。平台类型通过OnCheckPlatfrom()检测,构造阶段(this.config未初始化)放行,热更新阶段类型不匹配则返回错误。
4.4 DeepSeek
interface DeepSeekConfig extends Config {
type: "DeepSeek";
thinking?: { type: "enabled" | "disabled" };
userId?: string;
}
// 默认: deepseek-v4-flash | https://api.deepseek.com/chat/completions
// 鉴权: Authorization: Bearer <apiKey>
// 思考模式: thinking: { type: "enabled" } → response.thinkingContent
// 结构化输出: API 仅支持 json_object,传入对象时自动 JSON.stringify 注入 system prompt (见 4.10)4.5 OpenAI
interface OpenAIConfig extends Config {
type: "OpenAI";
reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
responseFormat?: "text" | "json_object" | "json_schema";
jsonSchema?: { name: string; schema: Record<string, any>; strict?: boolean }; // 配合 "json_schema" 使用,也可直接通过基类 responseFormat 传入对象
streamOptions?: { include_usage?: boolean };
modalities?: Array<"text" | "audio">;
store?: boolean;
serviceTier?: "auto" | "default" | "flex" | "priority";
safetyIdentifier?: string;
webSearchOptions?: { searchContextSize?: "low" | "medium" | "high" };
parallelToolCalls?: boolean;
}
// 默认: gpt-4o | https://api.openai.com/v1/chat/completions
// 鉴权: Authorization: Bearer <apiKey>
// 思考模式: reasoningEffort + 支持推理的模型 → response.thinkingContent4.6 Claude
interface ClaudeConfig extends Config {
type: "Claude";
thinking?: { type: "enabled" | "disabled" | "adaptive"; budgetTokens?: number };
outputConfig?: { effort?: "low" | "medium" | "high" | "xhigh" | "max" };
speed?: "standard" | "fast";
userId?: string;
}
// 默认: claude-sonnet-4-6 | https://api.anthropic.com/v1/messages
// 鉴权: x-api-key: <apiKey> + anthropic-version: 2023-06-01
// 注意: Opus 4.7+ 中 temperature/topP/topK 已弃用,仅接受 1.0
// 思考模式: thinking: { type: "enabled" } → response.thinkingContent
// 结构化输出: 原生支持 output_config.format,传入对象时自动映射 (见 4.10)4.7 Gemini
interface GeminiConfig extends Config {
type: "Gemini";
topK?: number;
candidateCount?: number;
thinkingConfig?: { includeThoughts?: boolean; thinkingBudget?: number };
safetySettings?: Array<{ category: string; threshold: string }>;
responseModalities?: Array<"TEXT" | "IMAGE">;
}
// 默认: gemini-2.5-flash | ...googleapis.com/v1beta/models/{model}:generateContent
// 鉴权: ?key=<apiKey> 查询参数 | 流式: ?alt=sse&key=<apiKey>
// URL 中的 {model} 由 OnResolveUrl() 在请求时动态替换
// 思考模式: thinkingConfig: { includeThoughts: true } → response.thinkingContent
// 结构化输出: 原生支持 responseJsonSchema,传入对象时自动映射 (见 4.10)4.8 QWen(通义千问)
interface QWenConfig extends Config {
type: "QWen";
enableSearch?: boolean;
vlHighResolutionImages?: boolean;
repetitionPenalty?: number;
thinking?: { type: "enabled" | "disabled" };
}
// 默认: qwen-plus | https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
// 鉴权: Authorization: Bearer <apiKey>
// 思考模式: thinking: { type: "enabled" } → response.thinkingContent
// 结构化输出: API 仅支持 json_object 模式,传入对象时自动 JSON.stringify 注入 system prompt 并设置 response_format: { type: "json_object" }4.9 Ollama
interface OllamaConfig extends Config {
type: "Ollama";
keep_alive?: number | string;
think?: boolean | "high" | "medium" | "low" | "max";
options?: Record<string, unknown>;
}
// 默认: llama3.2 | http://localhost:11434/api/chat
// 无需 apiKey | 流式使用 NDJSON 格式(非 SSE)
// 思考模式: think: true (或 "high"/"medium"/"low"/"max") → response.thinkingContent
// 结构化输出: format 参数原生支持 JSON Schema 对象,传入对象时直接透传4.10 结构化输出 (responseFormat)
responseFormat 接受 string | Record<string, any>,用户可传入字符串模式名或任意 JSON Schema 对象,各平台自动映射为各自的原生参数:
const llm = AbstractLLM.OnCreat({
type: "OpenAI",
model: "gpt-4o",
apiKey: "...",
responseFormat: {
type: "object",
properties: {
name: { type: "string", description: "姓名" },
age: { type: "number", description: "年龄" }
},
required: ["name", "age"]
}
});| 平台 | 映射方式 | 机制 |
|---|---|---|
| OpenAI | response_format: { type: "json_schema", json_schema: { name: "response", strict: true, schema } } | 原生 constrained decoding |
| Claude | output_config: { format: { type: "json_schema", schema } } | 原生 constrained decoding (2026.2 GA) |
| Gemini | generationConfig: { responseMimeType: "application/json", responseJsonSchema: schema } | 原生 constrained decoding |
| Ollama | format: schema | 原生 JSON Schema 透传 |
| DeepSeek | response_format: { type: "json_object" } + JSON.stringify(schema) 注入 system prompt | API 不支持 json_schema,将 schema 对象序列化后注入提示词引导模型 |
| QWen | response_format: { type: "json_object" } + JSON.stringify(schema) 注入 system prompt | API 不支持 json_schema,将 schema 对象序列化后注入提示词引导模型 |
DeepSeek/QWen 降级策略: 这两个平台 API 不支持原生 JSON Schema 约束。框架将传入的对象通过
JSON.stringify序列化后拼入 system prompt,同时设置response_format: { type: "json_object" }。用户无需关心平台差异。
注意: OpenAI 同时保留了
jsonSchema扩展字段({ name, schema, strict? }),传入字符串"json_schema"时可配合使用,用于需要自定义name或关闭strict的场景。
5. 系统提示词
系统提示词通过 Config.systemPrompt 传入,构造时自动消费。后续 OnSetConfig 传入不同值时也会更新:
const llm = AbstractLLM.OnCreat({
type: "DeepSeek",
model: "deepseek-chat",
apiKey: "sk-xxx",
systemPrompt: "你是一个资深的后端架构师,使用 TypeScript 回答。",
});
// 更新系统提示词(仅当与缓存中不同时生效)
llm.OnSetConfig({ systemPrompt: "你现在是一个前端专家。" });内部流程:
- 构造函数或
OnSetConfig合并配置 → 比较config.systemPrompt与缓存中的系统消息 - 若不同 → 调用
this.OnAddSystemMessage(text)替换messages[0] - 系统消息始终作为
messages[0]持久化,Trim 不裁剪 - 请求时各平台
OnBuildBody()通过this.messages.OnGetSystem()获取并按需转换
6. 工具定义与调用
工具符合 MCP InputSchema 规范,LLM 在合适场景下通过 tool_calls 调用:
// 定义工具
const tools: LLMUtility.ToolData[] = [{
name: "calculator",
description: "执行四则运算",
inputSchema: {
type: "object",
properties: {
expression: { type: "string", description: "数学表达式" },
},
required: ["expression"],
},
handler: async (args) => {
const expr = String(args.expression);
const result = Function(`"use strict"; return (${expr})`)();
return JSON.stringify({ expression: expr, result });
},
}];
// 传入 OnSend,框架自动处理工具调用循环
const response = await llm.OnSend("计算 3 + 5 * 2", tools);
console.log(response.content); // 含工具调用结果的最终回复工具入参校验: 框架在调用 handler 前会使用 inputSchema 校验 toolCall.arguments,发现 required 缺失、类型不符、枚举越界等会直接返回错误给 LLM 促使其纠正。
7. 对话请求
7.1 非流式对话
const response = await llm.OnSend("你好");
console.log(response.content); // 完整回复文本
console.log(response.thinkingContent); // 思考/推理内容(需平台支持 thinking mode)
console.log(response.finishReason); // "stop" | "length" | "tool_calls" | "content_filter" | "unknown"
console.log(response.usage); // { input: 50, output: 30, total: 80 }
console.log(response.toolCalls); // 工具调用列表 (如有)7.2 流式对话
const response = await llm.OnSend("讲一个故事", null, (chunk) => {
if (chunk.content) {
process.stdout.write(chunk.content); // 实时增量文本
}
if (chunk.thinkingContent) {
// 思考增量 — 与 content 互斥,同一 chunk 不同时包含两者
process.stderr.write(chunk.thinkingContent);
}
if (chunk.finishReason) {
console.log("\n结束:", chunk.finishReason);
}
});
// response.content 为完整文本,response.thinkingContent 为完整思考内容
// response.usage 为最终计费用量7.3 工具调用流程
当 LLM 返回 finishReason: "tool_calls" 时,框架自动进入工具调用循环:
User Prompt → OnRequest → Response (finishReason="tool_calls")
→ OnExecuteTool(toolCall1, tool) → OnAddToolMessage(result1)
→ OnExecuteTool(toolCall2, tool) → OnAddToolMessage(result2)
→ OnRequest (含工具结果) → Response (finishReason="stop")循环上限 100 轮,防止死循环。工具调用中间的流式回调不会触发,避免干扰最终输出。
也可手动执行工具:
const response = await llm.OnSend("计算 15.7 * 3.2");
for (const tc of response.toolCalls || []) {
const tool = tools.find(t => t.name === tc.name);
const result = await llm.OnExecuteTool(tc, tool);
console.log(`${tc.name} → ${result.data}`);
}7.4 思考模式 (Thinking)
部分平台支持"思考模式"(Thinking/Reasoning),模型在给出最终回复前会生成内部推理过程。框架将思考内容提取到 response.thinkingContent,并与 content 分离存储。
平台启用方式:
| 平台 | 配置字段 | 示例 |
|---|---|---|
| DeepSeek | thinking: { type: "enabled" } | 默认关闭 |
| OpenAI | reasoningEffort: "medium" | 需支持推理的模型(如 o-series) |
| Claude | thinking: { type: "enabled", budgetTokens: 4000 } | 支持 "enabled" / "disabled" / "adaptive" |
| Gemini | thinkingConfig: { includeThoughts: true } | 可指定 thinkingBudget 控制 token 预算 |
| QWen | thinking: { type: "enabled" } | 默认 { type: "disabled" } |
| Ollama | think: true 或 "high" / "medium" / "low" / "max" | 需模型支持(如 qwen3 系列) |
同步获取:
const llm = AbstractLLM.OnCreat({
type: "DeepSeek",
model: "deepseek-chat",
apiKey: "sk-xxx",
thinking: { type: "enabled" },
});
const r = await llm.OnSend("13.8 和 13.11 哪个大?请推理后给出答案。");
console.log(r.content); // 正式回复
console.log(r.thinkingContent); // 模型内部推理过程(可能为空)流式获取:
let thinking = "";
let text = "";
const r = await llm.OnSend("推导勾股定理", null, (chunk) => {
if (chunk.thinkingContent) thinking += chunk.thinkingContent;
if (chunk.content) text += chunk.content;
});
// thinking === r.thinkingContent — 流式拼接与最终响应一致
// text === r.content
content与thinkingContent互斥:流式回调中,同一StreamChunk不同时包含content和thinkingContent。模型先输出思考内容,再输出正式回复。
多轮持久化:
thinkingContent 会自动写入 MessageData 并在后续请求中回填到平台 API 消息中,确保多轮对话时模型能"看到"之前的推理上下文。跨平台迁移时 thinkingContent 同样会被保留。
注意:Claude 的 thinking 回填需要
signature字段,当前版本暂不支持。其余平台(DeepSeek/OpenAI/QWen/Gemini/Ollama)均完整支持 thinking 的提取、流式输出和多轮回填。
8. 消息缓存与轮次管理
消息缓存
Message 负责管理 LLM 上下文消息。每次 OnSend 前自动执行:
OnCompactMessages()— 截断过长的工具输出(默认 > 800 字符),由基类统一实现messages.OnTrim()— 混合裁剪(Token 硬限制 + 轮次软限制)
Token 容量与剔除策略
maxCache 语义为 Token 容量上限(默认 8000)。Message 内部自动计算 maxRounds(maxCache / 80,最小 10)作为轮次辅助限制。
裁剪规则:
- Token 硬限制:当
OnGetTokenCount() > maxCache * 0.8时,删除最旧完整轮次直至安全水位 - 轮次软限制:当
OnGetRoundCount() > maxRounds时,删除最旧完整轮次
maxCache <= 0 时不限制。系统消息 messages[0] 永不裁剪,剔除单元为完整一轮。
Token 计数
OnGetTokenCount() 基于消息内容字符数估算(~3.5 字符/token),不依赖 API 返回的 usage 数据:
public OnGetTokenCount(): number {
let total = 0;
for (const msg of this.messages) {
total += Math.ceil((msg.content?.length ?? 0) / 3.5);
if (msg.toolCalls?.length) {
total += Math.ceil(JSON.stringify(msg.toolCalls).length / 3.5);
}
if (msg.thinkingContent?.length) {
total += Math.ceil(msg.thinkingContent.length / 3.5);
}
}
return total;
}设计说明:API 返回的
usage是每次 HTTP 请求的独立用量(工具调用循环中各请求的 prompt 有大量重叠),适合计费但不适合缓存大小估算。OnGetTokenCount()使用字符估算保持一致性。
Token 用量追踪(计费)
API usage 数据由 Message.OnAdd 自动累加并 stamp 到每条 assistant 消息上。Response.usage 由 Message.OnGetRoundUsage() 填充,即为该轮累计计费用量,外部直接累加即可:
let totalBilling = 0;
const r1 = await llm.OnSend("第一问");
totalBilling += r1.usage.total; // 该轮计费用量
const r2 = await llm.OnSend("第二问");
totalBilling += r2.usage.total;注意:不要从
messages遍历求和所有 assistant 的usage——同一轮内的中间 assistant 持有部分快照,会重复计数。Response.usage已处理此问题。
9. 状态导出与还原
支持完整的 LLM 状态快照,用于会话持久化。导出/导入直接使用 MessageData[]:
// 导出 — 返回全部消息列表
const snapshot: LLMUtility.MessageData[] = llm.OnExport();
console.log(`导出: ${snapshot.length} 条消息`);
// 还原 — 创建时传入快照
const restored = AbstractLLM.OnCreat(
{ type: "DeepSeek", model: "deepseek-chat", apiKey: "sk-xxx" },
snapshot,
);
// 或分步导入
const llm2 = AbstractLLM.OnCreat({ type: "DeepSeek", model: "...", apiKey: "..." });
llm2.OnImport(snapshot);
// 继续对话
await restored.OnSend("刚才我们聊到哪了?");快照优先级:
OnImport(data)传入的快照代表精确的对话状态,会完全替换当前消息缓存。若快照中不含系统消息,不会自动从Config.systemPrompt补充。如需在还原时添加系统提示词,请在快照messages[0]手动插入一条{ role: "system", content: "...", timestamp: Date.now() }消息。
10. 并发控制
同一实例同一时间只能有一个 OnSend 在执行,并发调用会立即返回错误:
const [a, b] = await Promise.all([
llm.OnSend("请求A"),
llm.OnSend("请求B"),
]);
// a.content === "LLM is busy, a previous OnSend() is still in progress"
// b 正常返回解决方案: 创建多个实例:
const llm1 = AbstractLLM.OnCreat({ type: "DeepSeek", model: "...", apiKey: "sk-aaa" });
const llm2 = AbstractLLM.OnCreat({ type: "DeepSeek", model: "...", apiKey: "sk-bbb" });
const [r1, r2] = await Promise.all([
llm1.OnSend("问题1"),
llm2.OnSend("问题2"),
]);11. 工具参考
11.1 LLMUtility
核心类型命名空间:
配置类型:
| 导出 | 说明 |
|---|---|
| Config | 通用 LLM 配置接口 |
| CheckResult | { valid: boolean, error?: string } |
消息类型:
| 导出 | 说明 |
|---|---|
| MessageData | 统一消息接口 (role, content, timestamp, toolCalls?, toolCallId?, name?, usage?, thinkingContent?) |
| Message | 消息缓存类(管理 MessageData[],含轮次裁剪与消息压缩) |
| Response | 对话响应(平台 OnRequest 返回不含 messages,OnSend 补充 messages 和累计 usage) |
工具类型:
| 导出 | 说明 |
|---|---|
| ToolData | 工具定义 (name, description, inputSchema, handler) |
| ToolCall | LLM 返回的工具调用 (id, name, arguments) |
| ToolResult | 工具执行结果 (id, name, data) |
| JSONSchema | 工具入参规范(符合 MCP 规范) |
响应类型:
| 导出 | 说明 |
|---|---|
| Response | 统一响应 (id, model, content, thinkingContent?, toolCalls?, finishReason, usage, raw, messages?) |
| StreamChunk | 流式分块 (content, thinkingContent? 互斥, finishReason?) |
| StreamCallback | (chunk: StreamChunk) => void |
| Usage | Token 用量 (input, output, total) |
| FinishReason | "stop" \| "length" \| "tool_calls" \| "content_filter" \| "unknown" |
11.2 NetworkUtility
// HTTP 请求
const res = await NetworkUtility.HTTP.OnRequest<T>(url, {
method: "post",
data: body,
headers: { "Content-Type": "application/json" },
timeout: 30000,
});
// res.data: T | res.status | res.statusText | res.ok
// SSE 流式请求
for await (const line of NetworkUtility.HTTP.OnStreamRequest(url, options)) {
// line 是单行 JSON 字符串
}
// HttpError
try { ... } catch (e) {
if (e instanceof NetworkUtility.HTTP.HttpError) {
console.log(e.status, e.statusText, e.data);
}
}
// WebSocket
const ws = new NetworkUtility.Websocket();
ws.On("open", () => console.log("connected"));
ws.On("message", (ws, event) => console.log(event.data));
ws.OnConnect("wss://example.com/ws");
ws.OnSend(JSON.stringify({ type: "ping" }));
ws.OnDisConnect();11.3 StringUtility
| 函数 | 说明 |
|---|---|
| empty(str): boolean | 判空(null / undefined / 纯空白) |
| GetHash(key): number | 字符串哈希值 |
| GetGUID(): string | UUID v4 |
11.4 LogUtility
| 函数 | 说明 |
|---|---|
| SetSwitch(value: boolean) | 全局日志开关 |
| LogTip(...msgs) | 提示(绿色) |
| LogInfo(...msgs) | 信息(蓝色) |
| LogWarning(...msgs) | 警告(橙色) |
| LogError(...msgs) | 错误(红色) |
| LogSystem(...msgs) | 系统(紫色) |
前缀格式: [KNIGHT][类型标签] 消息内容
11.5 ObjectUtility
// 数组池
new ObjectUtility.ArrayPool<MyType>();
// Add / Remove / Get / Exist
// 键值池
new ObjectUtility.MapPool<MyType>();
// Add / Remove / Get / Set / KeysToArray / ValuesToArray
// 队列
new ObjectUtility.QueuePool<MyType>();
// Enqueue / Dequeue / Peek
// 反射
ObjectUtility.OnGetFunctionSignature(fn);
// → { functionName, parameters: [{ name, type?, defaultValue?, optional? }] }
// 类型判断
ObjectUtility.IsNonEmptyObject(value);
// → true 当 value 是非空普通对象(排除 null、数组、空对象)11.6 InterfaceUtility
type IType<T> = new (...args: any[]) => T; // 构造函数类型约束
type IAction<T, K, M, N, Q, U, V, W>; // 泛型回调
type IEvent<T, K, M, N, Q, U, V, W>;11.7 TimeUtility / PromiseUtility
TimeUtility.Now(); // Date.now()
TimeUtility.NowString(); // "2026-07-15 14:30:00"
TimeUtility.NowHEX(16); // Date.now().toString(16)
PromiseUtility.Wait(1000); // await 1 秒12. 高级用法
12.1 扩展自定义 LLM
import { AbstractLLM, RegisterLLM, LLMUtility, NetworkUtility } from "knight-agent";
// 1. 定义平台类型
namespace MyLLMType {
export interface Config extends LLMUtility.Config { type: "MyLLM"; customOption?: string; }
export interface ChatResponse { /* ... */ }
}
// 2. 实现 — 需覆写 OnBuildBody、OnStopReason 和 OnRequest;OnSend/OnAdd*/OnCompactMessages 由基类提供
@RegisterLLM("MyLLM")
class MyLLM extends AbstractLLM<MyLLMType.Config> {
public constructor(config?: MyLLMType.Config) {
super({ type: "MyLLM", url: "https://api.myllm.com/chat", model: "default", apiKey: "", timeout: 60000, ...config });
}
protected OnBuildBody(messages: LLMUtility.MessageData[], tools: LLMUtility.ToolData[], stream: boolean): Record<string, unknown> {
// 将统一 MessageData[] 映射为平台 API wire format
return {
model: this.config.model,
messages: messages.map(m => ({ role: m.role, content: m.content })),
// ... 平台特定字段
};
}
protected OnStopReason(reason: string): LLMUtility.FinishReason {
switch (reason) { case "stop": return "stop"; default: return "unknown"; }
}
protected async OnRequest(tools: LLMUtility.ToolData[], onChunk?: LLMUtility.StreamCallback): Promise<LLMUtility.Response> {
const body = this.OnBuildBody(this.messages.OnGetAll(), tools, false);
const response = await NetworkUtility.HTTP.OnRequest<MyLLMType.ChatResponse>(this.config.url, {
method: "post",
data: body,
headers: { "Authorization": `Bearer ${this.config.apiKey}`, "Content-Type": "application/json" },
timeout: this.config.timeout,
});
// 将平台响应映射为 LLMUtility.Response ...
}
}要点: 构造函数必须将用户 config 展开在平台默认值之后(
{ ...defaults, ...config }),确保默认值不被覆盖。OnSend主流程、OnAdd*消息管理和OnCompactMessages工具输出截断均由基类提供,无需覆写。仅需覆写三个抽象方法:OnBuildBody(统一消息 → 平台 API wire format 映射)、OnStopReason(停止原因码映射)和OnRequest(HTTP 请求与响应解析)。
12.2 实例管理与并发
// 静态池管理
AbstractLLM.OnCreat(config1);
AbstractLLM.OnCreat(config2);
// 两个实例均在 AbstractLLM.llms 中
// 多实例并发
const [r1, r2] = await Promise.all([
llm1.OnSend("问题1"),
llm2.OnSend("问题2"),
]);
// 销毁
llm1.OnDestroy(); // 从池中移除,清理缓存12.3 错误处理
// OnSend 在以下情况返回错误 Response(不抛异常):
// - config 为空: content = "LLM config is not initialized..."
// - prompt 为空: content = "Prompt is empty or null"
// - 并发冲突: content = "LLM is busy..."
// OnSetConfig 返回 CheckResult:
const result = llm.OnSetConfig(config);
if (!result.valid) {
console.error("配置错误:", result.error);
}
// 工具入参校验错误会返回:
// { id: toolCall.id, name: toolCall.name, data: '{"error":"missing required argument \\"query\\""}' }
// HTTP / 网络错误会抛出异常:
try { await llm.OnSend("hello"); } catch (e) {
if (e instanceof NetworkUtility.HTTP.HttpError) {
// HTTP 层错误: e.status, e.statusText, e.data
}
}13. 完整示例
import { AbstractLLM, LLMUtility, LogUtility } from "knight-agent";
async function main() {
// ===== 1. 创建实例 =====
const llm = AbstractLLM.OnCreat({
type: "DeepSeek",
model: "deepseek-chat",
apiKey: process.env.DEEPSEEK_API_KEY!,
stream: true,
temperature: 0.7,
maxCache: 8000,
systemPrompt: "你是一个技术文档助手,用简洁清晰的中文回答。",
});
if (!llm) { console.error("创建失败"); return; }
console.log(`实例 ID: ${llm.id}`);
// ===== 2. 结构化输出(可选,跨平台自动适配) =====
// const llm2 = AbstractLLM.OnCreat({
// type: "OpenAI",
// model: "gpt-4o",
// apiKey: "...",
// responseFormat: {
// type: "object",
// properties: {
// summary: { type: "string", description: "摘要" },
// keywords: { type: "array", items: { type: "string" } }
// },
// required: ["summary"]
// }
// });
// ===== 3. 定义工具 =====
const tools: LLMUtility.ToolData[] = [{
name: "search_docs",
description: "搜索技术文档",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "搜索关键词" },
limit: { type: "number", description: "返回数量", minimum: 1, maximum: 10 },
},
required: ["query"],
},
handler: async (args) => {
const results = await searchDocs(String(args.query), Number(args.limit) || 5);
return JSON.stringify(results);
},
}];
// ===== 4. 流式对话 =====
LogUtility.LogSystem("开始对话");
const r1 = await llm.OnSend("React useEffect 依赖数组为空是什么行为?", tools, (chunk) => {
if (chunk.content) process.stdout.write(chunk.content);
});
console.log(`\n用量: ${JSON.stringify(r1.usage)}`);
// ===== 5. 多轮对话 =====
const r2 = await llm.OnSend("那如果传入 [count] 呢?", tools);
console.log(r2.content);
// ===== 6. 导出状态 =====
const snapshot = llm.OnExport();
console.log(`消息数: ${snapshot.length} 条`);
console.log(`轮次: ${snapshot.filter(m => m.role === "user").length} 轮`);
// ===== 7. 查看对话历史(从消息列表提取) =====
logHistory(snapshot);
// ===== 8. 清理 =====
llm.OnDestroy();
}
function logHistory(messages: LLMUtility.MessageData[]) {
console.log("\n=== 对话历史 ===");
let totalUsage = { input: 0, output: 0, total: 0 };
for (let i = 0; i < messages.length; i++) {
const m = messages[i];
const time = new Date(m.timestamp).toLocaleTimeString();
if (m.role === "user") {
console.log(`[${time}] 用户: ${m.content.slice(0, 50)}...`);
} else if (m.role === "assistant") {
console.log(`[${time}] 助手: ${m.content.slice(0, 80)}...`);
// 只取每轮最后一条 assistant(下一条是 user 或末尾),避免重复计数
const next = messages[i + 1];
if (m.usage && (!next || next.role === "user")) {
console.log(` 本轮用量: in=${m.usage.input} out=${m.usage.output}`);
totalUsage.input += m.usage.input;
totalUsage.output += m.usage.output;
totalUsage.total += m.usage.total;
}
}
}
console.log(`累计用量: ${JSON.stringify(totalUsage)}`);
}
main().catch(console.error);