Skip to content

Commit b61cb94

Browse files
committed
Add opentelemetry instrumentions
1 parent 20d8fe3 commit b61cb94

File tree

4 files changed

+194
-9
lines changed

4 files changed

+194
-9
lines changed

src/fastapi_app/__init__.py

+17-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
import os
44

55
import azure.identity.aio
6+
import fastapi
7+
from azure.monitor.opentelemetry import configure_azure_monitor
68
from dotenv import load_dotenv
79
from environs import Env
8-
from fastapi import FastAPI
10+
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
11+
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
912

1013
from .globals import global_storage
1114
from .openai_clients import create_openai_chat_client
@@ -15,7 +18,7 @@
1518

1619

1720
@contextlib.asynccontextmanager
18-
async def lifespan(app: FastAPI):
21+
async def lifespan(app: fastapi.FastAPI):
1922
load_dotenv(override=True)
2023

2124
azure_credential = None
@@ -40,6 +43,8 @@ async def lifespan(app: FastAPI):
4043
global_storage.openai_chat_client = openai_chat_client
4144
global_storage.openai_chat_model = openai_chat_model
4245

46+
if os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"):
47+
SQLAlchemyInstrumentor().instrument(engine=engine.sync_engine)
4348
yield
4449

4550
await engine.dispose()
@@ -54,7 +59,16 @@ def create_app():
5459
else:
5560
logging.basicConfig(level=logging.WARNING)
5661

57-
app = FastAPI(docs_url="/docs", lifespan=lifespan)
62+
# Turn off particularly noisy INFO level logs from Azure Core SDK:
63+
logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING)
64+
65+
if os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"):
66+
logger.info("Configuring Azure Monitor")
67+
configure_azure_monitor(logger_name="ragapp")
68+
# OpenAI SDK requests use httpx and are thus not auto-instrumented:
69+
OpenAIInstrumentor().instrument()
70+
71+
app = fastapi.FastAPI(docs_url="/docs", lifespan=lifespan)
5872

5973
from . import api_routes # noqa
6074
from . import frontend_routes # noqa

src/fastapi_app/dependencies.py

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import logging
2+
import os
3+
from collections.abc import AsyncGenerator
4+
from typing import Annotated
5+
6+
import azure.identity
7+
from fastapi import Depends, Request
8+
from openai import AsyncAzureOpenAI, AsyncOpenAI
9+
from pydantic import BaseModel
10+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
11+
12+
logger = logging.getLogger("ragapp")
13+
14+
15+
class OpenAIClient(BaseModel):
16+
"""
17+
OpenAI client
18+
"""
19+
20+
client: AsyncOpenAI | AsyncAzureOpenAI
21+
model_config = {"arbitrary_types_allowed": True}
22+
23+
24+
class FastAPIAppContext(BaseModel):
25+
"""
26+
Context for the FastAPI app
27+
"""
28+
29+
openai_chat_model: str
30+
openai_chat_deployment: str
31+
32+
33+
async def common_parameters():
34+
"""
35+
Get the common parameters for the FastAPI app
36+
"""
37+
OPENAI_CHAT_HOST = os.getenv("OPENAI_CHAT_HOST")
38+
39+
if OPENAI_CHAT_HOST == "azure":
40+
openai_chat_deployment = os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT", "gpt-35-turbo")
41+
openai_chat_model = os.getenv("AZURE_OPENAI_CHAT_MODEL", "gpt-35-turbo")
42+
elif OPENAI_CHAT_HOST == "ollama":
43+
openai_chat_deployment = "phi3:3.8b"
44+
openai_chat_model = os.getenv("OLLAMA_CHAT_MODEL", "phi3:3.8b")
45+
else:
46+
openai_chat_deployment = "gpt-3.5-turbo"
47+
openai_chat_model = os.getenv("OPENAICOM_CHAT_MODEL", "gpt-3.5-turbo")
48+
return FastAPIAppContext(
49+
openai_chat_model=openai_chat_model,
50+
openai_chat_deployment=openai_chat_deployment,
51+
)
52+
53+
54+
async def get_azure_credentials() -> azure.identity.DefaultAzureCredential | azure.identity.ManagedIdentityCredential:
55+
azure_credential: azure.identity.DefaultAzureCredential | azure.identity.ManagedIdentityCredential
56+
try:
57+
if client_id := os.getenv("APP_IDENTITY_ID"):
58+
# Authenticate using a user-assigned managed identity on Azure
59+
# See web.bicep for value of APP_IDENTITY_ID
60+
logger.info(
61+
"Using managed identity for client ID %s",
62+
client_id,
63+
)
64+
azure_credential = azure.identity.ManagedIdentityCredential(client_id=client_id)
65+
else:
66+
azure_credential = azure.identity.DefaultAzureCredential()
67+
return azure_credential
68+
except Exception as e:
69+
logger.warning("Failed to authenticate to Azure: %s", e)
70+
raise e
71+
72+
73+
async def create_async_sessionmaker(engine: AsyncEngine) -> async_sessionmaker[AsyncSession]:
74+
"""Get the agent database"""
75+
return async_sessionmaker(
76+
engine,
77+
expire_on_commit=False,
78+
autoflush=False,
79+
)
80+
81+
82+
async def get_async_sessionmaker(
83+
request: Request,
84+
) -> AsyncGenerator[async_sessionmaker[AsyncSession], None]:
85+
yield request.state.sessionmaker
86+
87+
88+
async def get_context(
89+
request: Request,
90+
) -> FastAPIAppContext:
91+
return request.state.context
92+
93+
94+
async def get_async_db_session(
95+
sessionmaker: Annotated[async_sessionmaker[AsyncSession], Depends(get_async_sessionmaker)],
96+
) -> AsyncGenerator[AsyncSession, None]:
97+
async with sessionmaker() as session:
98+
yield session
99+
100+
101+
async def get_openai_chat_client(
102+
request: Request,
103+
) -> OpenAIClient:
104+
"""Get the OpenAI chat client"""
105+
return OpenAIClient(client=request.state.chat_client)
106+
107+
108+
async def get_openai_embed_client(
109+
request: Request,
110+
) -> OpenAIClient:
111+
"""Get the OpenAI embed client"""
112+
return OpenAIClient(client=request.state.embed_client)
113+
114+
115+
CommonDeps = Annotated[FastAPIAppContext, Depends(get_context)]
116+
DBSession = Annotated[AsyncSession, Depends(get_async_db_session)]
117+
ChatClient = Annotated[OpenAIClient, Depends(get_openai_chat_client)]
118+
EmbeddingsClient = Annotated[OpenAIClient, Depends(get_openai_embed_client)]

src/pyproject.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ dependencies = [
1515
"pgvector",
1616
"openai",
1717
"tiktoken",
18-
"openai-messages-token-helper"
18+
"openai-messages-token-helper",
19+
"opentelemetry-instrumentation-fastapi>=0.46b0,<1.0.0",
1920
]
2021

2122
[build-system]

src/requirements.txt

+57-5
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,39 @@ aiosignal==1.3.1
33
annotated-types==0.7.0
44
anyio==4.4.0
55
asgiref==3.8.1
6+
asttokens==2.4.1
67
async-timeout==4.0.3
78
asyncpg==0.29.0
89
attrs==23.2.0
910
azure-core==1.30.1
11+
azure-core-tracing-opentelemetry==1.0.0b11
1012
azure-identity==1.16.0
13+
azure-monitor-opentelemetry==1.6.0
1114
azure-monitor-opentelemetry-exporter==1.0.0b27
15+
backcall==0.2.0
16+
beautifulsoup4==4.12.3
17+
bleach==6.1.0
1218
build==1.2.1
1319
certifi==2024.2.2
1420
cffi==1.16.0
1521
cfgv==3.4.0
1622
charset-normalizer==3.3.2
1723
click==8.1.7
1824
cryptography==42.0.7
25+
decorator==5.1.1
26+
defusedxml==0.7.1
1927
Deprecated==1.2.14
2028
distlib==0.3.8
2129
distro==1.9.0
2230
dnspython==2.6.1
31+
docopt==0.6.2
2332
email_validator==2.1.1
2433
environs==11.0.0
34+
executing==2.0.1
2535
fastapi==0.111.0
2636
fastapi-cli==0.0.4
27-
-e git+https://github.com/chatrtham/rag-postgres-openai-python.git@7f1578f2d48381331570f68cc13167c3290ee097#egg=fastapi_app&subdirectory=src
37+
-e git+https://github.com/chatrtham/rag-postgres-openai-python.git@20d8fe30d4373e9290dbd18c012f6610393fd5e7#egg=fastapi_app&subdirectory=src
38+
fastjsonschema==2.20.0
2839
filelock==3.14.0
2940
fixedint==0.1.6
3041
frozenlist==1.4.1
@@ -41,16 +52,28 @@ httpx==0.27.0
4152
identify==2.5.36
4253
idna==3.7
4354
importlib_metadata==7.1.0
55+
ipython==8.12.3
4456
isodate==0.6.1
57+
jedi==0.19.1
4558
Jinja2==3.1.4
59+
jsonschema==4.23.0
60+
jsonschema-specifications==2023.12.1
61+
jupyter_client==8.6.2
62+
jupyter_core==5.7.2
63+
jupyterlab_pygments==0.3.0
4664
markdown-it-py==3.0.0
4765
MarkupSafe==2.1.5
4866
marshmallow==3.21.2
67+
matplotlib-inline==0.1.7
4968
mdurl==0.1.2
69+
mistune==3.0.2
5070
msal==1.28.0
5171
msal-extensions==1.1.0
5272
msrest==0.7.1
5373
multidict==6.0.5
74+
nbclient==0.10.0
75+
nbconvert==7.16.4
76+
nbformat==5.10.4
5477
nodeenv==1.9.1
5578
numpy==1.26.4
5679
oauthlib==3.2.2
@@ -62,25 +85,43 @@ opentelemetry-exporter-otlp-proto-common==1.25.0
6285
opentelemetry-exporter-otlp-proto-grpc==1.25.0
6386
opentelemetry-exporter-otlp-proto-http==1.25.0
6487
opentelemetry-instrumentation==0.46b0
65-
opentelemetry-instrumentation-asgi==0.46b0
66-
opentelemetry-instrumentation-fastapi==0.46b0
67-
opentelemetry-instrumentation-requests==0.46b0
88+
opentelemetry-instrumentation-asgi==0.47b0
89+
opentelemetry-instrumentation-dbapi==0.47b0
90+
opentelemetry-instrumentation-django==0.47b0
91+
opentelemetry-instrumentation-fastapi==0.47b0
92+
opentelemetry-instrumentation-flask==0.47b0
93+
opentelemetry-instrumentation-openai==0.26.0
94+
opentelemetry-instrumentation-psycopg2==0.47b0
95+
opentelemetry-instrumentation-requests==0.47b0
6896
opentelemetry-instrumentation-sqlalchemy==0.46b0
97+
opentelemetry-instrumentation-urllib==0.47b0
98+
opentelemetry-instrumentation-urllib3==0.47b0
99+
opentelemetry-instrumentation-wsgi==0.47b0
69100
opentelemetry-proto==1.25.0
101+
opentelemetry-resource-detector-azure==0.1.5
70102
opentelemetry-sdk==1.25.0
71103
opentelemetry-semantic-conventions==0.46b0
72-
opentelemetry-util-http==0.46b0
104+
opentelemetry-semantic-conventions-ai==0.3.6
105+
opentelemetry-util-http==0.47b0
73106
orjson==3.10.3
74107
packaging==24.0
75108
pandas==2.2.2
109+
pandocfilters==1.5.1
110+
parso==0.8.4
111+
pexpect==4.9.0
76112
pgvector==0.2.5
113+
pickleshare==0.7.5
77114
pillow==10.3.0
78115
pip-tools==7.4.1
116+
pipreqs==0.5.0
79117
platformdirs==4.2.2
80118
portalocker==2.8.2
81119
pre-commit==3.7.1
120+
prompt_toolkit==3.0.47
82121
protobuf==4.25.3
83122
psutil==5.9.8
123+
ptyprocess==0.7.0
124+
pure_eval==0.2.3
84125
pycparser==2.22
85126
pydantic==2.7.2
86127
pydantic_core==2.18.3
@@ -92,20 +133,28 @@ python-dotenv==1.0.1
92133
python-multipart==0.0.9
93134
pytz==2024.1
94135
PyYAML==6.0.1
136+
pyzmq==26.0.3
137+
referencing==0.35.1
95138
regex==2024.5.15
96139
requests==2.32.3
97140
requests-oauthlib==2.0.0
98141
rich==13.7.1
142+
rpds-py==0.19.1
99143
ruff==0.4.8
100144
shellingham==1.5.4
101145
six==1.16.0
102146
smmap==5.0.1
103147
sniffio==1.3.1
148+
soupsieve==2.5
104149
SQLAlchemy==2.0.30
150+
stack-data==0.6.3
105151
starlette==0.37.2
106152
tenacity==8.4.1
107153
tiktoken==0.7.0
154+
tinycss2==1.3.0
155+
tornado==6.4.1
108156
tqdm==4.66.4
157+
traitlets==5.14.3
109158
typer==0.12.3
110159
typing_extensions==4.12.0
111160
tzdata==2024.1
@@ -115,7 +164,10 @@ uvicorn==0.30.0
115164
uvloop==0.19.0
116165
virtualenv==20.26.2
117166
watchfiles==0.22.0
167+
wcwidth==0.2.13
168+
webencodings==0.5.1
118169
websockets==12.0
119170
wrapt==1.16.0
171+
yarg==0.1.9
120172
yarl==1.9.4
121173
zipp==3.19.2

0 commit comments

Comments
 (0)