-
Notifications
You must be signed in to change notification settings - Fork 366
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #378 from TomasBack2Future/feat/glue
add glue extension
- Loading branch information
Showing
12 changed files
with
739 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# | ||
# This file is part of TEN Framework, an open source project. | ||
# Licensed under the Apache License, Version 2.0. | ||
# See the LICENSE file for more information. | ||
# | ||
import("//build/feature/ten_package.gni") | ||
|
||
ten_package("glue_python_async") { | ||
package_kind = "extension" | ||
|
||
resources = [ | ||
"__init__.py", | ||
"addon.py", | ||
"extension.py", | ||
"log.py", | ||
"manifest.json", | ||
"property.json", | ||
"tests", | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# glue_python_async | ||
|
||
This is a python extension for glue service. The schema of glue service is attached in `schema.yml`. | ||
|
||
An example of OpenAI wrapper is also attached in `examples/openai_wrapper.py`. | ||
|
||
## Features | ||
|
||
The extension will record history with count of `max_history`. | ||
|
||
- `api_url` (must have): the url for the glue service. | ||
- `token` (must have): use Bearer token to support default auth | ||
|
||
The extension support flush that will close the existing http session. | ||
|
||
## API | ||
|
||
Refer to `api` definition in [manifest.json] and default values in [property.json](property.json). | ||
|
||
- In: | ||
- `text_data` [data]: the asr result | ||
- `flush` [cmd]: the flush signal | ||
- Out: | ||
- `flush` [cmd]: the flush signal | ||
|
||
## Examples | ||
|
||
You can run example using following command, and the wrapper service will listen 8000 by default. | ||
|
||
``` | ||
> export API_TOKEN="xxx" && export OPENAI_API_KEY="xxx" && python3 openai_wrapper.py | ||
INFO: Started server process [162886] | ||
INFO: Waiting for application startup. | ||
INFO: Application startup complete. | ||
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# | ||
# This file is part of TEN Framework, an open source project. | ||
# Licensed under the Apache License, Version 2.0. | ||
# See the LICENSE file for more information. | ||
# | ||
from . import addon | ||
from .log import logger | ||
|
||
logger.info("glue_python_async extension loaded") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# | ||
# This file is part of TEN Framework, an open source project. | ||
# Licensed under the Apache License, Version 2.0. | ||
# See the LICENSE file for more information. | ||
# | ||
from ten import ( | ||
Addon, | ||
register_addon_as_extension, | ||
TenEnv, | ||
) | ||
from .extension import AsyncGlueExtension | ||
from .log import logger | ||
|
||
|
||
@register_addon_as_extension("glue_python_async") | ||
class AsyncGlueExtensionAddon(Addon): | ||
|
||
def on_create_instance(self, ten_env: TenEnv, name: str, context) -> None: | ||
logger.info("AsyncGlueExtensionAddon on_create_instance") | ||
ten_env.on_create_instance_done(AsyncGlueExtension(name), context) |
123 changes: 123 additions & 0 deletions
123
agents/ten_packages/extension/glue_python_async/examples/openai_wrapper.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import os | ||
import openai | ||
from openai import AsyncOpenAI | ||
import traceback # Add this import | ||
|
||
from typing import List, Union | ||
from pydantic import BaseModel, HttpUrl | ||
|
||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials | ||
from fastapi.responses import StreamingResponse | ||
from fastapi import Depends, FastAPI, HTTPException, Request | ||
import asyncio | ||
|
||
app = FastAPI(title="Chat Completion API", | ||
description="API for streaming chat completions with support for text, image, and audio content", | ||
version="1.0.0") | ||
|
||
# Set your OpenAI API key | ||
openai.api_key = os.getenv("OPENAI_API_KEY") | ||
|
||
class TextContent(BaseModel): | ||
type: str = "text" | ||
text: str | ||
|
||
class ImageContent(BaseModel): | ||
type: str = "image" | ||
image_url: HttpUrl | ||
|
||
class AudioContent(BaseModel): | ||
type: str = "audio" | ||
audio_url: HttpUrl | ||
|
||
class Message(BaseModel): | ||
role: str | ||
content: Union[TextContent, ImageContent, AudioContent, List[Union[TextContent, ImageContent, AudioContent]]] | ||
|
||
class ChatCompletionRequest(BaseModel): | ||
messages: List[Message] | ||
model: str | ||
temperature: float = 1.0 | ||
stream: bool = True | ||
|
||
def format_openai_messages(messages): | ||
formatted_messages = [] | ||
for msg in messages: | ||
if isinstance(msg.content, list): | ||
content = [] | ||
for item in msg.content: | ||
if item.type == "text": | ||
content.append({"type": "text", "text": item.text}) | ||
elif item.type == "image": | ||
content.append({"type": "image_url", "image_url": str(item.image_url)}) | ||
elif item.type == "audio": | ||
content.append({"type": "audio_url", "audio_url": str(item.audio_url)}) | ||
else: | ||
if msg.content.type == "text": | ||
content = [{"type": "text", "text": msg.content.text}] | ||
elif msg.content.type == "image": | ||
content = [{"type": "image_url", "image_url": {"url": str(msg.content.image_url)}}] | ||
elif msg.content.type == "audio": | ||
content = [{"type": "audio_url", "audio_url": {"url": str(msg.content.audio_url)}}] | ||
|
||
formatted_messages.append({"role": msg.role, "content": content}) | ||
return formatted_messages | ||
|
||
security = HTTPBearer() | ||
|
||
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)): | ||
token = credentials.credentials | ||
if token != os.getenv("API_TOKEN"): | ||
raise HTTPException(status_code=403, detail="Invalid or missing token") | ||
|
||
@app.post("/chat/completions", dependencies=[Depends(verify_token)]) | ||
async def create_chat_completion(request: ChatCompletionRequest, req: Request): | ||
try: | ||
messages = format_openai_messages(request.messages) | ||
client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY")) | ||
response = await client.chat.completions.create( | ||
model=request.model, | ||
messages=messages, | ||
temperature=request.temperature, | ||
stream=request.stream | ||
) | ||
|
||
async def generate(): | ||
try: | ||
async for chunk in response: | ||
if chunk.choices[0].delta.content is not None: | ||
yield f"data: {chunk.choices[0].delta.content}\n\n" | ||
yield "data: [DONE]\n\n" | ||
except asyncio.CancelledError: | ||
print("Request was cancelled") | ||
raise | ||
|
||
return StreamingResponse(generate(), media_type="text/event-stream") | ||
except asyncio.CancelledError: | ||
print("Request was cancelled") | ||
raise HTTPException(status_code=499, detail="Request was cancelled") | ||
except Exception as e: | ||
traceback_str = ''.join(traceback.format_tb(e.__traceback__)) | ||
error_message = f"{str(e)}\n{traceback_str}" | ||
print(error_message) | ||
raise HTTPException(status_code=500, detail=error_message) | ||
|
||
if __name__ == "__main__": | ||
import uvicorn | ||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials | ||
from fastapi import Depends | ||
import traceback | ||
|
||
''' | ||
http_proxy = os.getenv("HTTP_PROXY") | ||
https_proxy = os.getenv("HTTPS_PROXY") | ||
if http_proxy or https_proxy: | ||
proxies = { | ||
"http": http_proxy, | ||
"https": https_proxy | ||
} | ||
openai.proxy = proxies | ||
''' | ||
|
||
uvicorn.run(app, host="0.0.0.0", port=8000) |
3 changes: 3 additions & 0 deletions
3
agents/ten_packages/extension/glue_python_async/examples/requirements.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
uvicorn | ||
fastapi | ||
openai |
Oops, something went wrong.