Skip to content

Commit ba4e5ba

Browse files
committed
added telemetry
1 parent 1bfde46 commit ba4e5ba

File tree

8 files changed

+306
-48
lines changed

8 files changed

+306
-48
lines changed

.github/workflows/azure-container-api.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ jobs:
6161
"AZURE_OPENAI_ENDPOINT=secretref:azure-openai-endpoint" \
6262
"AZURE_OPENAI_API_KEY=secretref:azure-openai-api-key" \
6363
"AZURE_VOICE_ENDPOINT=secretref:azure-voice-endpoint" \
64-
"AZURE_VOICE_KEY=secretref:azure-voice-key"
64+
"AZURE_VOICE_KEY=secretref:azure-voice-key" \
65+
"APPINSIGHTS_CONNECTIONSTRING=secretref:appinsights-connectionstring" \
66+
"LOCAL_TRACING_ENABLED=false"
6567
6668
- name: Logout
6769
run: |

api/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import asyncio
2+
from functools import wraps
3+
4+
5+
def repeat(*, seconds: float):
6+
def decorator(func):
7+
@wraps(func)
8+
async def wrapper(*args, **kwargs):
9+
async def loop(*args, **kwargs):
10+
while True:
11+
try:
12+
await func(*args, **kwargs)
13+
except Exception as e:
14+
print(f"Error: {e}")
15+
await asyncio.sleep(seconds)
16+
asyncio.ensure_future(loop(*args, **kwargs))
17+
return wrapper
18+
return decorator
19+
20+
21+
# Compare this snippet from .venv/Lib/site-packages/fastapi_utilities/repeat/repeat_every.py:
22+
# slimmed down version of the repeat_every decorator

api/main.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from fastapi.responses import StreamingResponse
77
from jinja2 import Environment, FileSystemLoader
88
from rtclient import RTLowLevelClient
9+
from api import repeat
910
from api.realtime import RealtimeVoiceClient
1011
from api.session import Message, RealtimeSession, SessionManager
1112
from contextlib import asynccontextmanager
@@ -14,13 +15,20 @@
1415
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
1516
from api.session import SessionManager
1617
from api.suggestions import SimpleMessage, create_suggestion, suggestion_requested
18+
from prompty.tracer import Tracer, trace
1719
from dotenv import load_dotenv
20+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
21+
22+
from api.telemetry import init_tracing
1823

1924
load_dotenv()
2025

2126
AZURE_VOICE_ENDPOINT = os.getenv("AZURE_VOICE_ENDPOINT")
2227
AZURE_VOICE_KEY = os.getenv("AZURE_VOICE_KEY")
2328

29+
LOCAL_TRACING_ENABLED = os.getenv("LOCAL_TRACING_ENABLED", "true") == "true"
30+
init_tracing(local_tracing=LOCAL_TRACING_ENABLED)
31+
2432
base_path = Path(__file__).parent
2533

2634
# Load products and purchases
@@ -38,7 +46,6 @@
3846
async def lifespan(app: FastAPI):
3947
try:
4048
# manage lifetime scope
41-
pass
4249
yield
4350
finally:
4451
# remove all stray sockets
@@ -107,12 +114,13 @@ async def voice_endpoint(websocket: WebSocket):
107114
key_credential=AzureKeyCredential(AZURE_VOICE_KEY),
108115
azure_deployment="gpt-4o-realtime-preview",
109116
) as rt:
110-
117+
111118
# get current messages for instructions
112119
chat_items = await websocket.receive_json()
113120
message = Message(**chat_items)
114121

115122
# create voice system message
123+
# TODO: retrieve context from chat messages via thread id
116124
system_message = env.get_template("script.jinja2").render(
117125
customer="Seth",
118126
purchases=purchases,
@@ -130,3 +138,12 @@ async def voice_endpoint(websocket: WebSocket):
130138

131139
except WebSocketDisconnect as e:
132140
print("Voice Socket Disconnected", e)
141+
142+
143+
@repeat(seconds=60)
144+
@trace
145+
async def cleanup_sessions():
146+
await SessionManager.clear_closed_sessions()
147+
148+
149+
FastAPIInstrumentor.instrument_app(app, exclude_spans=["send", "receive"])

api/realtime.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from dataclasses import dataclass
2+
import json
23
from typing import Any, AsyncGenerator
34
from rtclient import (
45
InputAudioBufferAppendMessage,
@@ -47,6 +48,7 @@ async def close(self):
4748
if self.client is not None:
4849
await self.client.close()
4950

51+
@trace
5052
async def send_user_message(self, message: str):
5153
if self.client is None:
5254
raise Exception("Client not set")
@@ -68,6 +70,7 @@ async def send_user_message(self, message: str):
6870
)
6971
)
7072

73+
@trace
7174
async def send_user_message_with_response(self, message: str):
7275
if self.client is None:
7376
raise Exception("Client not set")
@@ -95,11 +98,13 @@ async def send_user_message_with_response(self, message: str):
9598

9699
await self.client.send(ResponseCreateMessage(response=response))
97100

101+
@trace
98102
async def trigger_response(self):
99103
if self.client is None:
100104
raise Exception("Client not set")
101105
await self.client.send(ResponseCreateMessage())
102106

107+
@trace
103108
async def send_system_message(self, message: str):
104109
if self.client is None:
105110
raise Exception("Client not set")
@@ -121,6 +126,7 @@ async def send_system_message(self, message: str):
121126
)
122127
)
123128

129+
@trace
124130
async def send_session_update(self, instructions: str = None):
125131
if self.client is None:
126132
raise Exception("Client not set")
@@ -149,7 +155,6 @@ async def send_audio_message(self, audio_data: Any):
149155

150156
await self.client.send(InputAudioBufferAppendMessage(audio=audio_data))
151157

152-
@trace(name="realtime_loop")
153158
async def receive_message(self) -> AsyncGenerator[RealTimeItem, None]:
154159
if self.client is None:
155160
raise Exception("Client not set")
@@ -164,9 +169,14 @@ async def receive_message(self) -> AsyncGenerator[RealTimeItem, None]:
164169
print("Session Created Message")
165170
print(f" Model: {message.session.model}")
166171
print(f" Session Id: {message.session.id}")
167-
with Tracer.start("session.created") as tr:
168-
tr("model", message.session.model)
169-
tr("session_id", message.session.id)
172+
173+
yield RealTimeItem(
174+
type="session.created",
175+
content={
176+
"model": message.session.model,
177+
"session": message.session.id,
178+
},
179+
)
170180

171181
case "error":
172182
if self.verbose:

api/semantic-mapper.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"result.model.api": "gen_ai.operation.name",
3+
"result.object": "gen_ai.operation.name",
4+
"result.model.api.config.azure_deployment": "gen_ai.request.modelfile",
5+
"inputs.prompt.model.configuration.type": "gen_ai.system",
6+
"result.model": "gen_ai.response.model",
7+
"result.usage.total_tokens": "gen_ai.usage.input_tokens",
8+
"result.usage.completion_tokens": "gen_ai.usage.output_tokens",
9+
"result.usage.prompt_tokens": "gen_ai.usage.prompt_tokens",
10+
"inputs.data.id": "gen_ai.request.id",
11+
"result.id": "gen_ai.response.id",
12+
"create.attributes.0": "gen_ai.choice"
13+
}

0 commit comments

Comments
 (0)