@f5-sales-demo/pi-agent (英语)
具有工具执行和事件流的状态代理 。 建造在 @f5-sales-demo/pi-ai。 。 。 .
安装
npm install @f5-sales-demo/pi-agent
快速启动
import { Agent } from "@f5-sales-demo/pi-agent";
import { getModel } from "@f5-sales-demo/pi-ai";
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant.",
model: getModel("anthropic", "claude-sonnet-4-20250514"),
},
});
agent.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
// Stream just the new text chunk
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await agent.prompt("Hello!");
核心概念
代理Message vs LLM 信件
代理与 AgentMessage,灵活类型,可以包括:
- 标准 LLM 消息 (
user, (中文(简体) ),assistant, (中文(简体) ),toolResult(中文(简体) ) - 通过声明合并自定义应用特定消息类型
法学硕士只理解 user, (中文(简体) ), assistant,以及 toolResult编辑 convertToLlm 函数通过在每次LLM调用前过滤和转换消息来弥补这一空白.
信件流动
AgentMessage[] → transformContext() → AgentMessage[] → convertToLlm() → Message[] → LLM
(optional) (required)
- 转换语句: Prune 旧消息, 注入外部上下文
- 转换为Llm: 过滤出仅UI 信件, 将自定义类型转换为 LLM 格式
事件流程
代理为 UI 更新发布事件 。 了解事件序列有助于构建响应界面.
提示( ) 事件顺序
当你打电话 prompt("Hello")数字 :
prompt("Hello")
├─ agent_start
├─ turn_start
├─ message_start { message: userMessage } // Your prompt
├─ message_end { message: userMessage }
├─ message_start { message: assistantMessage } // LLM starts responding
├─ message_update { message: partial... } // Streaming chunks
├─ message_update { message: partial... }
├─ message_end { message: assistantMessage } // Complete response
├─ turn_end { message, toolResults: [] }
└─ agent_end { messages: [...] }
用工具调用
如果助手调用工具,循环继续:
prompt("Read config.json")
├─ agent_start
├─ turn_start
├─ message_start/end { userMessage }
├─ message_start { assistantMessage with toolCall }
├─ message_update...
├─ message_end { assistantMessage }
├─ tool_execution_start { toolCallId, toolName, args }
├─ tool_execution_update { partialResult } // If tool streams
├─ tool_execution_end { toolCallId, result }
├─ message_start/end { toolResultMessage }
├─ turn_end { message, toolResults: [toolResult] }
│
├─ turn_start // Next turn
├─ message_start { assistantMessage } // LLM responds to tool result
├─ message_update...
├─ message_end
├─ turn_end
└─ agent_end
持续( ) 事件顺序
continue() 从现有上下文恢复,而不添加新信件。 错误发生后用它进行重试 .
// After an error, retry from current state
await agent.continue();
上下文中的最后信息必须是 user 或者说 toolResult (无) assistant) (中文(简体) ).
事件类型
| 活动 | 说明 |
|---|---|
agent_start | 代理开始处理 |
agent_end | 代理完成所有新信件 |
turn_start | 新回合开始( 一个 LLM 调用 + 工具执行) |
turn_end | 以助理消息和工具结果转换完成 |
message_start | 任何消息开始( 用户、 助手、 工具Result) |
message_update | 仅限助理. 包括 assistantMessageEvent 带三角洲 |
message_end | 信件完成 |
tool_execution_start | 工具开始 |
tool_execution_update | 工具流进展 |
tool_execution_end | 工具完成 |
代理选项
const agent = new Agent({
// Initial state
initialState: {
systemPrompt: string,
model: Model,
thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh",
tools: AgentTool<any>[],
messages: AgentMessage[],
},
// Convert AgentMessage[] to LLM Message[] (required for custom message types)
convertToLlm: (messages) => messages.filter(...),
// Transform context before convertToLlm (for pruning, compaction)
transformContext: async (messages, signal) => pruneOldMessages(messages),
// How to handle queued messages: "one-at-a-time" (default) or "all"
queueMode: "one-at-a-time",
// Custom stream function (for proxy backends)
streamFn: streamProxy,
// Dynamic API key resolution (for expiring OAuth tokens)
getApiKey: async (provider) => refreshToken(),
// Tool execution context (late-bound UI/session access)
getToolContext: () => ({ /* app-defined */ }),
});
代理国
interface AgentState {
systemPrompt: string;
model: Model;
thinkingLevel: ThinkingLevel;
tools: AgentTool<any>[];
messages: AgentMessage[];
isStreaming: boolean;
streamMessage: AgentMessage | null; // Current partial during streaming
pendingToolCalls: Set<string>;
error?: string;
}
通过 agent.state在流出时, streamMessage 包含部分助理消息.
方法
提示
// Text prompt
await agent.prompt("Hello");
// With images
await agent.prompt("What's in this image?", [{ type: "image", data: base64Data, mimeType: "image/jpeg" }]);
// AgentMessage directly
await agent.prompt({ role: "user", content: "Hello", timestamp: Date.now() });
// Continue from current context (last message must be user or toolResult)
await agent.continue();
州管理层
agent.setSystemPrompt("New prompt");
agent.setModel(getModel("openai", "gpt-4o"));
agent.setThinkingLevel("medium");
agent.setTools([myTool]);
agent.replaceMessages(newMessages);
agent.appendMessage(message);
agent.clearMessages();
agent.reset(); // Clear everything
控制权
agent.abort(); // Cancel current operation
await agent.waitForIdle(); // Wait for completion
活动
const unsubscribe = agent.subscribe((event) => {
console.log(event.type);
});
unsubscribe();
指导与后续行动
在工具执行(stering)期间或代理商停止(后续)后要注入的队列信息 :
agent.setSteeringMode("one-at-a-time");
agent.setInterruptMode("immediate");
// While agent is running tools
agent.steer({
role: "user",
content: "Stop! Do this instead.",
timestamp: Date.now(),
});
// Queue a follow-up to run after the current turn completes
agent.followUp({
role: "user",
content: "After that, summarize the changes.",
timestamp: Date.now(),
});
每次工具默认调用后都会检查引导消息 。 设定 interruptMode 改为 "wait" 推迟
引导直到当前转向完成 .
自定义信件类型
扩展 AgentMessage 通过声明合并:
declare module "@f5-sales-demo/pi-agent" {
interface CustomAgentMessages {
notification: { role: "notification"; text: string; timestamp: number };
}
}
// Now valid
const msg: AgentMessage = { role: "notification", text: "Info", timestamp: Date.now() };
处理自定义类型 convertToLlm数字 :
const agent = new Agent({
convertToLlm: (messages) =>
messages.flatMap((m) => {
if (m.role === "notification") return []; // Filter out
return [m];
}),
});
工具
使用 AgentTool数字 :
import { Type } from "@sinclair/typebox";
const readFileTool: AgentTool = {
name: "read_file",
label: "Read File", // For UI display
description: "Read a file's contents",
parameters: Type.Object({
path: Type.String({ description: "File path" }),
}),
execute: async (toolCallId, params, signal, onUpdate, context) => {
const content = await fs.readFile(params.path, "utf-8");
// Optional: stream progress
onUpdate?.({ content: [{ type: "text", text: "Reading..." }], details: {} });
return {
content: [{ type: "text", text: content }],
details: { path: params.path, size: content.length },
};
},
};
agent.setTools([readFileTool]);
处理错误
丢出错误 当工具失败时。 不返回错误消息为内容 .
execute: async (toolCallId, params, signal, onUpdate) => {
if (!fs.existsSync(params.path)) {
throw new Error(`File not found: ${params.path}`);
}
// Return content only on success
return { content: [{ type: "text", text: "..." }] };
};
丢出的错误被代理抓住,并作为工具错误报告到 LLM isError: true。 。 。 .
代理用户
对于通过后端代理的浏览器应用:
import { Agent, streamProxy } from "@f5-sales-demo/pi-agent";
const agent = new Agent({
streamFn: (model, context, options) =>
streamProxy(model, context, {
...options,
authToken: "...",
proxyUrl: "https://your-server.com",
}),
});
低级 API
对于没有代理类的直接控制:
import { agentLoop, agentLoopContinue } from "@f5-sales-demo/pi-agent";
const context: AgentContext = {
systemPrompt: "You are helpful.",
messages: [],
tools: [],
};
const config: AgentLoopConfig = {
model: getModel("openai", "gpt-4o"),
convertToLlm: (msgs) => msgs.filter((m) => ["user", "assistant", "toolResult"].includes(m.role)),
};
const userMessage = { role: "user", content: "Hello", timestamp: Date.now() };
for await (const event of agentLoop([userMessage], context, config)) {
console.log(event.type);
}
// Continue from existing context
for await (const event of agentLoopContinue(context, config)) {
console.log(event.type);
}
许可证
麻省理工学院