Skip to content

Feat/api refactor #263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LanguageMap } from "./constant";
import { LanguageMap } from "@/common/constant";

export const voiceNameMap: LanguageMap = {
"zh-CN": {
Expand Down Expand Up @@ -43,6 +43,8 @@ export const voiceNameMap: LanguageMap = {
},
};

// Get the graph properties based on the graph name, language, and voice type
// This is the place where you can customize the properties for different graphs to override default property.json
export const getGraphProperties = (graphName: string, language: string, voiceType: string) => {
let localizationOptions = {
"greeting": "ASTRA agent connected. How can i help you today?",
Expand Down
50 changes: 50 additions & 0 deletions playground/src/app/api/agents/start/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { REQUEST_URL } from '@/common/constant';
import { NextRequest, NextResponse } from 'next/server';
import { getGraphProperties } from './graph';

/**
* Handles the POST request to start an agent.
*
* @param request - The NextRequest object representing the incoming request.
* @returns A NextResponse object representing the response to be sent back to the client.
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const {
request_id,
channel_name,
user_uid,
graph_name,
language,
voice_type,
} = body;

// Send a POST request to start the agent
const response = await fetch(`${REQUEST_URL}/start`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
request_id,
channel_name,
user_uid,
graph_name,
// Get the graph properties based on the graph name, language, and voice type
properties: getGraphProperties(graph_name, language, voice_type),
}),
});

const responseData = await response.json();

return NextResponse.json(responseData, { status: response.status });
} catch (error) {
if (error instanceof Response) {
const errorData = await error.json();
return NextResponse.json(errorData, { status: error.status });
} else {
return NextResponse.json({ code: "1", data: null, msg: "Internal Server Error" }, { status: 500 });
}
}
}
42 changes: 42 additions & 0 deletions playground/src/app/api/agents/stop/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { REQUEST_URL } from '@/common/constant';
import { NextRequest, NextResponse } from 'next/server';

/**
* Handles the POST request to stop an agent.
*
* @param request - The NextRequest object representing the incoming request.
* @returns A NextResponse object representing the response to be sent back to the client.
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const {
channel_name,
request_id,
} = body;

// Send a POST request to stop the agent
const response = await fetch(`${REQUEST_URL}/stop`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
request_id,
channel_name
}),
});

// Get the response data
const responseData = await response.json();

return NextResponse.json(responseData, { status: response.status });
} catch (error) {
if (error instanceof Response) {
const errorData = await error.json();
return NextResponse.json(errorData, { status: error.status });
} else {
return NextResponse.json({ code: "1", data: null, msg: "Internal Server Error" }, { status: 500 });
}
}
}
1 change: 0 additions & 1 deletion playground/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ export * from "./utils"
export * from "./storage"
export * from "./request"
export * from "./mock"
export * from "./graph"
20 changes: 12 additions & 8 deletions playground/src/common/request.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { AnyObject } from "antd/es/_util/type"
import { REQUEST_URL } from "./constant"
import { genUUID } from "./utils"
import { Language } from "@/types"

interface StartRequestConfig {
channel: string
userId: number,
graphName: string
properties: AnyObject
graphName: string,
language: Language,
voiceType: "male" | "female"
}

interface GenAgoraDataConfig {
Expand Down Expand Up @@ -34,15 +36,16 @@ export const apiGenAgoraData = async (config: GenAgoraDataConfig) => {
}

export const apiStartService = async (config: StartRequestConfig): Promise<any> => {
const url = `${REQUEST_URL}/start`
const { channel, userId, graphName, properties } = config
// look at app/api/agents/start/route.tsx for the server-side implementation
const url = `/api/agents/start`
const { channel, userId, graphName, language, voiceType } = config
const data = {
request_id: genUUID(),
channel_name: channel,
openai_proxy_url: "",
remote_stream_id: userId,
user_uid: userId,
graph_name: graphName,
properties,
language,
voice_type: voiceType
}
let resp: any = await fetch(url, {
method: "POST",
Expand All @@ -56,7 +59,8 @@ export const apiStartService = async (config: StartRequestConfig): Promise<any>
}

export const apiStopService = async (channel: string) => {
const url = `${REQUEST_URL}/stop`
// look at app/api/agents/stop/route.tsx for the server-side implementation
const url = `/api/agents/stop`
const data = {
request_id: genUUID(),
channel_name: channel
Expand Down
6 changes: 3 additions & 3 deletions playground/src/platform/mobile/description/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { setAgentConnected } from "@/store/reducers/global"
import {
DESCRIPTION, useAppDispatch, useAppSelector, apiPing, genUUID,
apiStartService, apiStopService,
getGraphProperties
apiStartService, apiStopService
} from "@/common"
import { message } from "antd"
import { useEffect, useState } from "react"
Expand Down Expand Up @@ -50,7 +49,8 @@ const Description = () => {
channel,
userId,
graphName,
properties: getGraphProperties(graphName, language, voiceType)
language,
voiceType
})
const { code, msg } = res || {}
if (code != 0) {
Expand Down
6 changes: 3 additions & 3 deletions playground/src/platform/pc/description/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { setAgentConnected } from "@/store/reducers/global"
import {
DESCRIPTION, useAppDispatch, useAppSelector, apiPing, genUUID,
apiStartService, apiStopService,
getGraphProperties
apiStartService, apiStopService
} from "@/common"
import { Select, Button, message, Upload } from "antd"
import { useEffect, useState, MouseEventHandler } from "react"
Expand Down Expand Up @@ -50,7 +49,8 @@ const Description = () => {
channel,
userId,
graphName,
properties: getGraphProperties(graphName, language, voiceType)
language,
voiceType
})
const { code, msg } = res || {}
if (code != 0) {
Expand Down
76 changes: 76 additions & 0 deletions server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
## Request & Response Examples
The server provides a simple layer for managing agent processes.

### API Resources

- [POST /start](#get-magazines)
- [POST /stop](#get-magazinesid)
- [POST /ping](#post-magazinesidarticles)


### POST /start
This api starts an agent with given graph and override properties. The started agent will join into the specified channel, and subscribe to the uid which your browser/device's rtc use to join.

| Param | Description |
| -------- | ------- |
| request_id | any uuid for tracing purpose |
| channel_name | channel name, it needs to be the same with the one your browser/device joins, agent needs to stay with your browser/device in the same channel to communicate |
| user_uid | the uid which your browser/device's rtc use to join, agent needs to know your rtc uid to subscribe your audio |
| bot_uid | optional, the uid bot used to join rtc |
| graph_name | the graph to be used when starting agent, will find in property.json |
| properties | additional properties to override in property.json, the override will not change original property.json, only the one agent used to start |
| timeout | determines how long the agent will remain active without receiving any pings. If the timeout is set to `-1`, the agent will not terminate due to inactivity. By default, the timeout is set to 60 seconds, but this can be adjusted using the `WORKER_QUIT_TIMEOUT_SECONDS` variable in your `.env` file. |

Example:
```bash
curl 'http://localhost:8080/start' \
-H 'Content-Type: application/json' \
--data-raw '{
"request_id": "c1912182-924c-4d15-a8bb-85063343077c",
"channel_name": "test",
"user_uid": 176573,
"graph_name": "camera.va.openai.azure",
"properties": {
"openai_chatgpt": {
"model": "gpt-4o"
}
}
}'
```

### POST /stop
This api stops the agent you started

| Param | Description |
| -------- | ------- |
| request_id | any uuid for tracing purpose |
| channel_name | channel name, the one you used to start the agent |

Example:
```bash
curl 'http://localhost:8080/stop' \
-H 'Content-Type: application/json' \
--data-raw '{
"request_id": "c1912182-924c-4d15-a8bb-85063343077c",
"channel_name": "test"
}'
```


### POST /ping
This api sends a ping to the server to indicate connection is still alive. This is not needed if you specify `timeout:-1` when starting the agent, otherwise the agent will quit if not receiving ping after timeout in seconds.

| Param | Description |
| -------- | ------- |
| request_id | any uuid for tracing purpose |
| channel_name | channel name, the one you used to start the agent |

Example:
```bash
curl 'http://localhost:8080/ping' \
-H 'Content-Type: application/json' \
--data-raw '{
"request_id": "c1912182-924c-4d15-a8bb-85063343077c",
"channel_name": "test"
}'
```
5 changes: 5 additions & 0 deletions server/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const (
PropertyJsonFile = "./agents/property.json"
// Token expire time
tokenExpirationInSeconds = uint32(86400)

WORKER_TIMEOUT_INFINITY = -1
)

var (
Expand All @@ -31,6 +33,9 @@ var (
"RemoteStreamId": {
{ExtensionName: extensionNameAgoraRTC, Property: "remote_stream_id"},
},
"BotStreamId": {
{ExtensionName: extensionNameAgoraRTC, Property: "uid"},
},
"Token": {
{ExtensionName: extensionNameAgoraRTC, Property: "token"},
},
Expand Down
31 changes: 28 additions & 3 deletions server/internal/http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ type StartReq struct {
RequestId string `json:"request_id,omitempty"`
ChannelName string `json:"channel_name,omitempty"`
GraphName string `json:"graph_name,omitempty"`
RemoteStreamId uint32 `json:"remote_stream_id,omitempty"`
RemoteStreamId uint32 `json:"user_uid,omitempty"`
BotStreamId uint32 `json:"bot_uid,omitempty"`
Token string `json:"token,omitempty"`
WorkerHttpServerPort int32 `json:"worker_http_server_port,omitempty"`
Properties map[string]map[string]interface{} `json:"properties,omitempty"`
QuitTimeoutSeconds int `json:"timeout,omitempty"`
}

type StopReq struct {
Expand Down Expand Up @@ -92,6 +94,22 @@ func (s *HttpServer) handlerHealth(c *gin.Context) {
s.output(c, codeOk, nil)
}

func (s *HttpServer) handlerList(c *gin.Context) {
slog.Info("handlerList start", logTag)
// Create a slice of maps to hold the filtered data
filtered := make([]map[string]interface{}, len(workers.Keys()))
for _, channelName := range workers.Keys() {
worker := workers.Get(channelName).(*Worker)
workerJson := map[string]interface{}{
"channelName": worker.ChannelName,
"createTs": worker.CreateTs,
}
filtered = append(filtered, workerJson)
}
slog.Info("handlerList end", logTag)
s.output(c, codeSuccess, filtered)
}

func (s *HttpServer) handlerPing(c *gin.Context) {
var req PingReq

Expand Down Expand Up @@ -163,7 +181,13 @@ func (s *HttpServer) handlerStart(c *gin.Context) {

worker := newWorker(req.ChannelName, logFile, s.config.Log2Stdout, propertyJsonFile)
worker.HttpServerPort = req.WorkerHttpServerPort
worker.QuitTimeoutSeconds = s.config.WorkerQuitTimeoutSeconds

if req.QuitTimeoutSeconds > 0 {
worker.QuitTimeoutSeconds = req.QuitTimeoutSeconds
} else {
worker.QuitTimeoutSeconds = s.config.WorkerQuitTimeoutSeconds
}

if err := worker.start(&req); err != nil {
slog.Error("handlerStart start worker failed", "err", err, "requestId", req.RequestId, logTag)
s.output(c, codeErrStartWorkerFailed, http.StatusInternalServerError)
Expand Down Expand Up @@ -461,9 +485,10 @@ func (s *HttpServer) Start() {

r.GET("/", s.handlerHealth)
r.GET("/health", s.handlerHealth)
r.POST("/ping", s.handlerPing)
r.GET("/list", s.handlerList)
r.POST("/start", s.handlerStart)
r.POST("/stop", s.handlerStop)
r.POST("/ping", s.handlerPing)
r.POST("/token/generate", s.handlerGenerateToken)
r.GET("/vector/document/preset/list", s.handlerVectorDocumentPresetList)
r.POST("/vector/document/update", s.handlerVectorDocumentUpdate)
Expand Down
Loading