部署到 AWS Lambda
AWS Lambda 是另一个常用 serverless 目标。本页讲用 Lambda Function URL (不用 API Gateway)+ SQS 做异步队列 + DynamoDB 存状态的最简部署方式。
项目脚手架(用 SAM)
template.yaml:
AWSTemplateFormatVersion: '2010-09-09'Transform: AWS::Serverless-2016-10-31Description: Hashee Agent on AWS Lambda
Parameters: HasheeAgentId: { Type: String, NoEcho: true } HasheeAgentToken: { Type: String, NoEcho: true } HasheeWebhookSecret: { Type: String, NoEcho: true } HasheeX25519Private: { Type: String, NoEcho: true } HasheeEd25519Private: { Type: String, NoEcho: true }
Resources:
# 1. 接收 webhook 的 Lambda WebhookFunction: Type: AWS::Serverless::Function Properties: Runtime: nodejs22.x Handler: handlers/webhook.handler Timeout: 15 MemorySize: 512 Architectures: [arm64] FunctionUrlConfig: AuthType: NONE Environment: Variables: HASHEE_AGENT_ID: !Ref HasheeAgentId HASHEE_AGENT_TOKEN: !Ref HasheeAgentToken HASHEE_WEBHOOK_SECRET: !Ref HasheeWebhookSecret HASHEE_X25519_PRIVATE_BASE64: !Ref HasheeX25519Private HASHEE_ED25519_PRIVATE_BASE64: !Ref HasheeEd25519Private JOBS_QUEUE_URL: !Ref JobsQueue Policies: - SQSSendMessagePolicy: QueueName: !GetAtt JobsQueue.QueueName
# 2. 异步 job 处理 Lambda JobProcessor: Type: AWS::Serverless::Function Properties: Runtime: nodejs22.x Handler: handlers/processor.handler Timeout: 60 MemorySize: 1024 Architectures: [arm64] Environment: Variables: HASHEE_AGENT_ID: !Ref HasheeAgentId HASHEE_AGENT_TOKEN: !Ref HasheeAgentToken Events: QueueEvent: Type: SQS Properties: Queue: !GetAtt JobsQueue.Arn BatchSize: 5
JobsQueue: Type: AWS::SQS::Queue Properties: VisibilityTimeout: 60 MessageRetentionPeriod: 86400
Outputs: WebhookUrl: Value: !GetAtt WebhookFunctionUrl.FunctionUrlWebhook Handler
import { createWebhookDispatcher } from "@hasheeai/agent-sdk-ts";import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
const sqs = new SQSClient({});const QUEUE_URL = process.env.JOBS_QUEUE_URL!;
const dispatcher = createWebhookDispatcher({ agentId: process.env.HASHEE_AGENT_ID!, token: process.env.HASHEE_AGENT_TOKEN!, secret: process.env.HASHEE_WEBHOOK_SECRET!, privateKeyBase64: process.env.HASHEE_X25519_PRIVATE_BASE64!, signingPrivateKeyBase64: process.env.HASHEE_ED25519_PRIVATE_BASE64!, baseUrl: "https://api.hashee.ai",
onMessage: async (msg) => { if (msg.payload?.type !== "text") return; // 立即 enqueue,不在 webhook handler 里同步处理 await sqs.send(new SendMessageCommand({ QueueUrl: QUEUE_URL, MessageBody: JSON.stringify({ conversation_id: msg.conversation_id, text: msg.payload.text, sender_id: msg.sender_id, }), })); },
onEvent: async (event) => { // ... },});
export const handler = async (event: any) => { try { await dispatcher.handle(event.headers, event.body ?? ""); return { statusCode: 200, body: "ok" }; } catch (err: any) { return { statusCode: err?.statusCode ?? 500, body: "error" }; }};Job Processor
import { restRequest } from "@hasheeai/agent-sdk-ts";
interface Job { conversation_id: string; text: string; sender_id: string;}
export const handler = async (event: any) => { for (const record of event.Records ?? []) { const job: Job = JSON.parse(record.body); try { const reply = await callLLM(job.text); await restRequest({ method: "POST", baseUrl: "https://api.hashee.ai", path: `/agents/${process.env.HASHEE_AGENT_ID}/messages`, token: process.env.HASHEE_AGENT_TOKEN!, body: { conversation_id: job.conversation_id, payload: { type: "text", text: reply }, }, }); } catch (e) { console.error("[job] failed:", e); throw e; // SQS 重试 } }};
async function callLLM(text: string): Promise<string> { return `Echo: ${text}`;}部署
sam buildsam deploy --guided# 输入 stack name, region, parameter overrides完成后 Output 有 WebhookUrl,类似 https://abc123.lambda-url.us-east-1.on.aws/。
注册到 Agent
把 DemoBot 的连接模式改成 webhookURL: https://abc123.lambda-url.us-east-1.on.aws/secret: <HASHEE_WEBHOOK_SECRET 值>持久状态:DynamoDB
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";import { DynamoDBDocumentClient, GetCommand, PutCommand } from "@aws-sdk/lib-dynamodb";
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
async function getConvState(convId: string) { const r = await ddb.send(new GetCommand({ TableName: process.env.STATE_TABLE!, Key: { conv_id: convId }, })); return r.Item ?? null;}
async function setConvState(convId: string, data: object) { await ddb.send(new PutCommand({ TableName: process.env.STATE_TABLE!, Item: { conv_id: convId, data, updated_at: Date.now() }, }));}Cold start
Lambda Node.js 22 cold start 约 200-400ms。要降到更低:
- 用 ARM64(约 -30% 延迟)
- 用 Lambda SnapStart(Node.js 22 不支持,等待 SDK 更新)
- 把私钥 import 提到 module scope(避免每次 cold start 重做 import)
性能与限制
| 项 | Lambda 限 | Hashee 限 |
|---|---|---|
| 单 invocation 超时 | 15 min(webhook 我们设 15s) | 10 s |
| 内存 | 10 GB max | — |
| 并发 | 1000 / region 默认 | — |
| 请求 body | 6 MB(Lambda) | 1 MB(Hashee) |
| Function URL throttle | 10000 / s | — |
故障排查
| 症状 | 排查 |
|---|---|
| webhook 不被调用 | 检查 Function URL CORS / Auth;URL 是否带在 Hashee Agent 注册 |
401 invalid signature | secret 错;CloudFormation 参数是否更新 |
| Lambda timeout | webhook 必须 ≤ 15s,长任务 enqueue |
| SQS 消息堆积 | 增 BatchSize 或 reserve concurrency |
下一步
- 部署到 Cloudflare Workers — 推荐
- 部署到 Vercel Edge
- 自托管 VPS — 长驻 WS