Skip to content

Commit

Permalink
feat: add llm based agent example
Browse files Browse the repository at this point in the history
  • Loading branch information
Areo-Joe committed Jan 16, 2025
1 parent d0fe169 commit 7af161b
Show file tree
Hide file tree
Showing 24 changed files with 1,948 additions and 0 deletions.
4 changes: 4 additions & 0 deletions cloudrunfunctions/llm-based-weather-agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
dist/
logs/
!.vscode
30 changes: 30 additions & 0 deletions cloudrunfunctions/llm-based-weather-agent/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "launch-tcb-ff-global",
// 注意:请确保全局安装了 @cloudbase/functions-framework 模块
// npm install -g @cloudbase/functions-framework
// 因不同环境下全局安装路径可能不同,需要手动替换
// Windows 系统可通过 `where tcb-ff` 命令查看全局安装路径
// MacOS/Linux 系统下可通过 `which tcb-ff` 命令查看全局安装路径
"program": "/Users/joe/.nvm/versions/node/v18.20.4/bin/tcb-ff",
// 直接启动 src 目录下的 ts 函数代码,如需调试编译后的 js 代码,可将 --source 参数替换为编译后的目录
"args": [
"--extendedContextKey=X-Functions-Extended-Context",
"--source cloudrunfunctions/src"
],
"env": {
// 默认支持调试 TS 代码,如不需要可以移除
"NODE_OPTIONS": "--require ts-node/register/transpile-only"
},
"skipFiles": ["<node_internals>/**"],
"outFiles": ["!**/node_modules/**"]
}
]
}
66 changes: 66 additions & 0 deletions cloudrunfunctions/llm-based-weather-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# 查询天气 Agent ———— 基于混元大模型

本模板提供了自定义 Agent 的云函数实现,通过调用大模型封装 Agent 接口,部署后提供以下 Agent 相关接口:

```shell
GET /:botId/records 聊天记录
POST /:botId/feedback 提交用户反馈
GET /:botId/feedback 获取用户反馈
GET /:botId/recommend-questions 获取推荐问题
GET /:botId/send-message 发送消息
```

其中,我们对这两个接口进行了简单实现,通过大模型能力构建了一个查询天气的 Agent,并将调用结果通过 SSE 返回。

- GET /:botId/recommend-questions 获取推荐问题
- GET /:botId/send-message Tcb 发送消息

其余的接口只做了空实现,保证返回值符合类型,但无真实功能,需要开发者自行实现。

- GET /:botId/records 聊天记录
- POST /:botId/feedback 提交用户反馈
- GET /:botId/feedback 获取用户反馈

## 目录结构

```shell
cloudrunfunctions/src
├── getChatRecord GET /:botId/records
│ ├── impl.ts
│ └── index.ts
├── getFeedback GET /:botId/feedback
│ ├── impl.ts
│ └── index.ts
├── index.ts 解析路径转发到具体实现函数
├── recommendQuestions GET /:botId/recommend-questions
│ ├── impl.ts
│ └── index.ts
├── sendFeedback POST /:botId/feedback
│ ├── impl.ts
│ └── index.ts
├── sendMessage GET /:botId/send-message
│ ├── impl.ts
│ └── index.ts
├── type.ts 类型定义
└── utils.ts 通用方法
```

## 部署须知

在部署至云函数前,请运行以下命令:

```shell
npm run preDeploy
```

## 使用 sdk 调用

部署后,可使用云开发 `@cloudbase/js-sdk` 进行调用。可参考:

- `/examples` 目录下的示例代码
- [通过 SDK 调用云开发 Agent](https://docs.cloudbase.net/ai/agent/sdk)

## 参考文档

- [云函数2.0](https://docs.cloudbase.net/cbrf/intro)
- [云开发 AI+](https://docs.cloudbase.net/ai/introduce)
10 changes: 10 additions & 0 deletions cloudrunfunctions/llm-based-weather-agent/cloudbase-functions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"functionsRoot": "./cloudrunfunctions/dist",
"functions": [
{
"name": "default",
"directory": "./",
"triggerPath": "/"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { GetChatRecordInput, GetChatRecordOutput } from "../type";

interface IGetChatRecordImpl extends GetChatRecordInput {
botId: string;
}
export async function getChatRecordImpl({
botId,
pageNumber,
pageSize,
sort,
}: IGetChatRecordImpl): Promise<GetChatRecordOutput> {
const ret: GetChatRecordOutput = {
recordList: [],
total: 0,
};

return ret;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
IntegrationResponse,
TcbEventFunction,
} from "@cloudbase/functions-typings";
import { createIntegrationResponse, parse } from "../utils";
import { GetChatRecordInput, GetChatRecordOutput } from "../type";
import { getChatRecordImpl } from "./impl";

export const getChatRecord: TcbEventFunction<
GetChatRecordInput,
Promise<GetChatRecordOutput | IntegrationResponse<{ message: string }>>
> = async function (event, context) {
const parseRes = parse(context.httpContext);

if (!parseRes) {
return createIntegrationResponse(400, `Cannot parse from HTTP context`);
}

const { botId } = parseRes;

return getChatRecordImpl({
...event,
botId,
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { GetFeedbackInput, GetFeedbackOutput } from "../type";

interface IGetFeedbackImpl extends GetFeedbackInput {
botId: string;
}
export async function getFeedbackImpl({
botId,
from,
maxRating,
minRating,
pageNumber,
pageSize,
sender,
senderFilter,
to,
type,
}: IGetFeedbackImpl): Promise<GetFeedbackOutput> {
const ret: GetFeedbackOutput = {
feedbackList: [],
total: 0,
};

return ret;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
IntegrationResponse,
TcbEventFunction,
} from "@cloudbase/functions-typings";
import { createIntegrationResponse, parse } from "../utils";
import { GetFeedbackInput, GetFeedbackOutput } from "../type";
import { getFeedbackImpl } from "./impl";

export const getFeedback: TcbEventFunction<
GetFeedbackInput,
Promise<GetFeedbackOutput | IntegrationResponse<{ message: string }>>
> = async function (event, context) {
const parseRes = parse(context.httpContext);

if (!parseRes) {
return createIntegrationResponse(400, `Cannot parse from HTTP context`);
}

const { botId } = parseRes;

return getFeedbackImpl({
...event,
botId,
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { HttpMethod, TcbEventFunction } from "@cloudbase/functions-typings";
import { createIntegrationResponse, parse } from "./utils";
import { sendMessage } from "./sendMessage";
import { getRecommendQuestions } from "./recommendQuestions";
import { getChatRecord } from "./getChatRecord";
import { getFeedback } from "./getFeedback";
import { sendFeedback } from "./sendFeedback";

const AGENT_ROUTES: Array<{
methodName: string;
httpMethod: HttpMethod;
target: TcbEventFunction;
}> = [
{ methodName: "send-message", httpMethod: "POST", target: sendMessage },
{
methodName: "recommend-questions",
httpMethod: "POST",
target: getRecommendQuestions,
},
{
methodName: "records",
httpMethod: "GET",
target: getChatRecord,
},
{
methodName: "feedback",
httpMethod: "GET",
target: getFeedback,
},
{
methodName: "feedback",
httpMethod: "POST",
target: sendFeedback,
},
];

export const main: TcbEventFunction<unknown> = function (event, context) {
if (!context.httpContext)
return createIntegrationResponse(500, "Invalid HTTP context");

const parseResult = parse(context.httpContext);
if (!parseResult) return createIntegrationResponse(404, "Invalid path");

const { httpMethod, methodName } = parseResult;
const route = AGENT_ROUTES.find(
(x) => x.methodName === methodName && x.httpMethod === httpMethod
);

if (route) {
return route.target(event, context);
} else {
return createIntegrationResponse(404, "Invalid path");
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
SendSSEData,
SendSSEEnding,
GetRecommendQuestionsInput,
GetRecommendQuestionsOutputChunk,
} from "../type";
import { getCloudbaseAi } from "../utils";

interface IGetRecommendQuestionsImpl extends GetRecommendQuestionsInput {
sendSSEData: SendSSEData<GetRecommendQuestionsOutputChunk>;
sendSSEEnding: SendSSEEnding;
botId: string;
envId: string;
}
export async function getRecommendQuestionsImpl({
envId,
botId,
sendSSEData,
sendSSEEnding,
}: IGetRecommendQuestionsImpl) {
const ai = await getCloudbaseAi(envId);

const model = ai.createModel("hunyuan-exp");
const res = await model.streamText({
model: "hunyuan-turbo",
messages: [
{
role: "system",
content:
"你是一个可以处理询问天气的机器人。用户可能会问你有关天气的问题,你可以调用工具函数进行查询。",
},
{
role: "user",
content: "你会推荐我问你什么问题?举三个例子。",
},
],
});
for await (const chunk of res.dataStream) {
sendSSEData({
content: chunk?.choices?.[0]?.delta?.content ?? "",
model: "hunyuan-turbo",
finish_reason: chunk?.choices?.[0]?.finish_reason ?? "",
});
}

sendSSEEnding();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
IntegrationResponse,
TcbEventFunction,
} from "@cloudbase/functions-typings";
import {
createIntegrationResponse,
parse,
sendData,
sendEnding,
} from "../utils";
import { GetRecommendQuestionsInput } from "../type";
import { getRecommendQuestionsImpl } from "./impl";

export const getRecommendQuestions: TcbEventFunction<
GetRecommendQuestionsInput,
Promise<string | IntegrationResponse<{ message: string }>>
> = async function (event, context) {
const parseRes = parse(context.httpContext);

if (!parseRes) {
return createIntegrationResponse(400, `Cannot parse from HTTP context`);
}

const envId = context.extendedContext?.envId;
if (typeof envId !== "string")
return createIntegrationResponse(400, "invalid envId");

const { botId } = parseRes;

const sse = context.sse?.();

if (sse && !sse.closed) {
sse.on("close", () => {});

try {
await getRecommendQuestionsImpl({
...event,
botId,
envId,
sendSSEData: (data) => sse && !sse.closed && sendData(sse, data),
sendSSEEnding: () => sse && !sse.closed && sendEnding(sse),
});
} catch (e) {
return createIntegrationResponse(
400,
`Get recommend questions from agent failed, detail: ${e}`
);
}
}
return "";
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { SendFeedbackOutput, SendFeedbackInput } from "../type";

interface ISendFeedbackImpl extends SendFeedbackInput {
botId: string;
}
export async function sendFeedbackImpl({
botId,
type,
aiAnswer,
comment,
input,
rating,
recordId,
tags,
}: ISendFeedbackImpl): Promise<SendFeedbackOutput> {
return { status: "success" };
}
Loading

0 comments on commit 7af161b

Please sign in to comment.