|
| 1 | +# Agent |
| 2 | + |
| 3 | +ChatLuna 提供了 Agent 的简易框架,可以让模型自主选择和调用工具。 |
| 4 | + |
| 5 | +## 创建 Agent Executor |
| 6 | + |
| 7 | +使用 `createAgentExecutor` 函数创建 Agent 执行器: |
| 8 | + |
| 9 | +```ts twoslash |
| 10 | +// @noImplicitAny: false |
| 11 | +// @strictNullChecks: false |
| 12 | +import { Context, Schema } from 'koishi' |
| 13 | + |
| 14 | +const ctx = new Context() |
| 15 | + |
| 16 | +// ---cut--- |
| 17 | +import type {} from "koishi-plugin-chatluna/services/chat"; |
| 18 | +import { createAgentExecutor, createToolsRef } from 'koishi-plugin-chatluna/llm-core/agent' |
| 19 | +import { ChatLunaChatPrompt } from 'koishi-plugin-chatluna/llm-core/chain/prompt' |
| 20 | +import type { ChatLunaTool } from 'koishi-plugin-chatluna/llm-core/platform/types' |
| 21 | +import { computed } from 'koishi-plugin-chatluna' |
| 22 | + |
| 23 | +const modelRef = await ctx.chatluna.createChatModel("openai/gpt-5-nano") |
| 24 | +const embeddingsRef = await ctx.chatluna.createEmbeddings("openai/text-embedding-3-small") |
| 25 | + |
| 26 | +const enabledTools = ['web-search', 'web-browser'] |
| 27 | +const toolsRef = computed(() => |
| 28 | + enabledTools.map( |
| 29 | + (toolName) => ctx.chatluna.platform.getTool(toolName) satisfies ChatLunaTool |
| 30 | + ) |
| 31 | +) |
| 32 | + |
| 33 | +const toolsRefCreated = createToolsRef({ |
| 34 | + tools: toolsRef, |
| 35 | + embeddings: embeddingsRef.value |
| 36 | +}) |
| 37 | + |
| 38 | +const prompt = new ChatLunaChatPrompt({ |
| 39 | + preset: ctx.chatluna.preset.getPreset('sydney'), |
| 40 | + tokenCounter: (text) => modelRef.value.getNumTokens(text), |
| 41 | + promptRenderService: ctx.chatluna.promptRenderer, |
| 42 | + sendTokenLimit: modelRef.value.getModelMaxContextSize() |
| 43 | +}) |
| 44 | + |
| 45 | +const executorRef = createAgentExecutor({ |
| 46 | + llm: modelRef, |
| 47 | + tools: toolsRefCreated.tools, |
| 48 | + prompt: prompt, |
| 49 | + agentMode: 'tool-calling', |
| 50 | + returnIntermediateSteps: true, |
| 51 | + handleParsingErrors: true |
| 52 | +}) |
| 53 | +``` |
| 54 | + |
| 55 | +<br> |
| 56 | + |
| 57 | +`createAgentExecutor` 接受以下参数: |
| 58 | + |
| 59 | +- `llm`: 语言模型的计算引用 |
| 60 | +- `tools`: 工具列表的计算引用 |
| 61 | +- `prompt`: 聊天提示词对象 |
| 62 | +- `agentMode`: Agent 模式,可选 `'tool-calling'` 或 `'react'` |
| 63 | +- `returnIntermediateSteps`: 是否返回中间步骤 |
| 64 | +- `handleParsingErrors`: 是否处理解析错误 |
| 65 | +- `instructions`: ReAct 模式下的额外指令 (可选) |
| 66 | + |
| 67 | +## 调用 Agent |
| 68 | + |
| 69 | +创建 Agent Executor 后,可以通过 `invoke` 方法调用: |
| 70 | + |
| 71 | +```ts twoslash |
| 72 | +// @noImplicitAny: false |
| 73 | +// @strictNullChecks: false |
| 74 | +// @noErrors |
| 75 | +import { Context, Schema } from 'koishi' |
| 76 | +import { HumanMessage, AIMessageChunk } from '@langchain/core/messages' |
| 77 | +import { createAgentExecutor, createToolsRef } from 'koishi-plugin-chatluna/llm-core/agent' |
| 78 | + |
| 79 | +const ctx = new Context() |
| 80 | +let executor: ReturnType<typeof createAgentExecutor> |
| 81 | + |
| 82 | +// ---cut--- |
| 83 | +const response = await executor.value.invoke({ |
| 84 | + input: new HumanMessage("搜索 OpenAI 的最新新闻"), |
| 85 | + chat_history: [], |
| 86 | + variables: {} |
| 87 | +}) |
| 88 | + |
| 89 | +const message = new AIMessage(response['output']) |
| 90 | + |
| 91 | +``` |
| 92 | + |
| 93 | +<br> |
| 94 | + |
| 95 | +调用后返回的结果包含: |
| 96 | + |
| 97 | +- `output`: Agent 的最终输出 |
| 98 | +- `intermediateSteps`: 中间步骤 (如果 `returnIntermediateSteps` 为 `true`) |
| 99 | + |
| 100 | +## Agent 模式 |
| 101 | + |
| 102 | +ChatLuna 支持两种 Agent 模式: |
| 103 | + |
| 104 | +### tool-calling 模式 |
| 105 | + |
| 106 | +使用模型的原生工具调用能力,适用于支持 Tool Calling 的模型 (如 GPT-5, Claude 等)。 |
| 107 | + |
| 108 | +```ts twoslash |
| 109 | +// @noImplicitAny: false |
| 110 | +// @strictNullChecks: false |
| 111 | +// @noErrors |
| 112 | +import { createAgentExecutor } from 'koishi-plugin-chatluna/llm-core/agent' |
| 113 | + |
| 114 | +const modelRef = await ctx.chatluna.createChatModel("openai/gpt-5-nano") |
| 115 | +const embeddingsRef = await ctx.chatluna.createEmbeddings("openai/text-embedding-3-small") |
| 116 | + |
| 117 | +const enabledTools = ['web-search', 'web-browser'] |
| 118 | +const toolsRef = computed(() => |
| 119 | + enabledTools.map( |
| 120 | + (toolName) => ctx.chatluna.platform.getTool(toolName) satisfies ChatLunaTool |
| 121 | + ) |
| 122 | +) |
| 123 | + |
| 124 | +const toolsRefCreated = createToolsRef({ |
| 125 | + tools: toolsRef, |
| 126 | + embeddings: embeddingsRef.value |
| 127 | +}) |
| 128 | + |
| 129 | +const prompt = new ChatLunaChatPrompt({ |
| 130 | + preset: ctx.chatluna.preset.getPreset('sydney'), |
| 131 | + tokenCounter: (text) => modelRef.value.getNumTokens(text), |
| 132 | + promptRenderService: ctx.chatluna.promptRenderer, |
| 133 | + sendTokenLimit: modelRef.value.getModelMaxContextSize() |
| 134 | +}) |
| 135 | + |
| 136 | +// ---cut--- |
| 137 | +const executorToolCallingRef = createAgentExecutor({ |
| 138 | + llm: modelRef, |
| 139 | + tools: toolsRefCreated.tools, |
| 140 | + prompt, |
| 141 | + agentMode: 'tool-calling', |
| 142 | + returnIntermediateSteps: true |
| 143 | +}) |
| 144 | +``` |
| 145 | + |
| 146 | +### react 模式 |
| 147 | + |
| 148 | +使用 ReAct (Reasoning and Acting) 模式,通过提示词引导模型进行推理和行动。 |
| 149 | + |
| 150 | +适用于不支持原生工具调用的模型。 |
| 151 | + |
| 152 | +```ts twoslash |
| 153 | +// @noImplicitAny: false |
| 154 | +// @strictNullChecks: false |
| 155 | +// @noErrors |
| 156 | +import { createAgentExecutor } from 'koishi-plugin-chatluna/llm-core/agent' |
| 157 | +import { computed } from 'koishi-plugin-chatluna' |
| 158 | + |
| 159 | +const modelRef = await ctx.chatluna.createChatModel("openai/gpt-5-nano") |
| 160 | +const embeddingsRef = await ctx.chatluna.createEmbeddings("openai/text-embedding-3-small") |
| 161 | + |
| 162 | +const enabledTools = ['web-search', 'web-browser'] |
| 163 | +const toolsRef = computed(() => |
| 164 | + enabledTools.map( |
| 165 | + (toolName) => ctx.chatluna.platform.getTool(toolName) satisfies ChatLunaTool |
| 166 | + ) |
| 167 | +) |
| 168 | + |
| 169 | +const toolsRefCreated = createToolsRef({ |
| 170 | + tools: toolsRef, |
| 171 | + embeddings: embeddingsRef.value |
| 172 | +}) |
| 173 | + |
| 174 | +const prompt = new ChatLunaChatPrompt({ |
| 175 | + preset: ctx.chatluna.preset.getPreset('sydney'), |
| 176 | + tokenCounter: (text) => modelRef.value.getNumTokens(text), |
| 177 | + promptRenderService: ctx.chatluna.promptRenderer, |
| 178 | + sendTokenLimit: modelRef.value.getModelMaxContextSize() |
| 179 | +}) |
| 180 | + |
| 181 | +// ---cut--- |
| 182 | +const executorReactRef = createAgentExecutor({ |
| 183 | + llm: modelRef, |
| 184 | + tools: toolsRefCreated.tools, |
| 185 | + prompt, |
| 186 | + agentMode: 'react', |
| 187 | + handleParsingErrors: true, |
| 188 | + instructions: computed(() => undefined) |
| 189 | +}) |
| 190 | +``` |
| 191 | + |
| 192 | +## 创建工具引用 |
| 193 | + |
| 194 | +使用 `createToolsRef` 函数创建响应式的工具引用。 |
| 195 | + |
| 196 | +工具引用配合 Agent 执行器的引用,可以实现工具的动态选择和 Agent 执行器的动态重建。 |
| 197 | + |
| 198 | +```ts twoslash |
| 199 | +// @noImplicitAny: false |
| 200 | +// @strictNullChecks: false |
| 201 | +import { Context, Schema } from 'koishi' |
| 202 | + |
| 203 | +const ctx = new Context() |
| 204 | +let session: Session |
| 205 | + |
| 206 | +// ---cut--- |
| 207 | +import type {} from "koishi-plugin-chatluna/services/chat"; |
| 208 | +import { createToolsRef } from 'koishi-plugin-chatluna/llm-core/agent' |
| 209 | +import { computed } from 'koishi-plugin-chatluna' |
| 210 | +import type { ChatLunaTool } from 'koishi-plugin-chatluna/llm-core/platform/types' |
| 211 | +import { HumanMessage } from '@langchain/core/messages' |
| 212 | + |
| 213 | +import { Session } from 'koishi' |
| 214 | + |
| 215 | +const embeddingsRef = await ctx.chatluna.createEmbeddings("openai/text-embedding-3-small") |
| 216 | + |
| 217 | +// 创建指定工具的引用 |
| 218 | +/* const toolsRef = computed(() => { |
| 219 | + const searchTool = ctx.chatluna.platform.getTool('web-search') |
| 220 | + const browserTool = ctx.chatluna.platform.getTool('web-browser') |
| 221 | + return [searchTool, browserTool] |
| 222 | +}) */ |
| 223 | + |
| 224 | +// 创建全部工具的引用 |
| 225 | +const toolsNameRef = await ctx.chatluna.platform.getTools() |
| 226 | +const toolsRef = computed(() => { |
| 227 | + return toolsNameRef.value.map((toolName) => { |
| 228 | + return ctx.chatluna.platform.getTool(toolName) satisfies ChatLunaTool |
| 229 | + }) |
| 230 | +}) |
| 231 | + |
| 232 | +const toolsRefCreated = createToolsRef({ |
| 233 | + tools: toolsRef, |
| 234 | + embeddings: embeddingsRef.value |
| 235 | +}) |
| 236 | + |
| 237 | + |
| 238 | +const messages = [new HumanMessage("你好")] |
| 239 | + |
| 240 | +toolsRefCreated.update(session, messages) |
| 241 | +``` |
| 242 | + |
| 243 | +<br> |
| 244 | + |
| 245 | +`createToolsRef` 会根据工具的 `selector` 和 `authorization` 方法,自动筛选出当前对话历史下可用的工具。 |
| 246 | + |
| 247 | +调用 `update` 方法可以更新工具列表。 |
0 commit comments