Skip to content
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

Azure AppInsights Monitoring using Open telemetry #475

Closed
wants to merge 3 commits into from
Closed
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
84 changes: 56 additions & 28 deletions app/backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import mimetypes
import time
import logging
from logging.handlers import RotatingFileHandler
import openai
from flask import Flask, request, jsonify, send_file, abort
from azure.identity import DefaultAzureCredential
Expand All @@ -12,6 +13,12 @@
from approaches.readdecomposeask import ReadDecomposeAsk
from approaches.chatreadretrieveread import ChatReadRetrieveReadApproach
from azure.storage.blob import BlobServiceClient
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from azure.monitor.opentelemetry.exporter import AzureMonitorTraceExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor

# Replace these with your own values, either in environment variables or directly here
AZURE_STORAGE_ACCOUNT = os.environ.get("AZURE_STORAGE_ACCOUNT") or "mystorageaccount"
Expand All @@ -23,6 +30,7 @@
AZURE_OPENAI_CHATGPT_DEPLOYMENT = os.environ.get("AZURE_OPENAI_CHATGPT_DEPLOYMENT") or "chat"
AZURE_OPENAI_CHATGPT_MODEL = os.environ.get("AZURE_OPENAI_CHATGPT_MODEL") or "gpt-35-turbo"
AZURE_OPENAI_EMB_DEPLOYMENT = os.environ.get("AZURE_OPENAI_EMB_DEPLOYMENT") or "embedding"
AZURE_MONITOR_CONNECTION_STRING = os.environ.get("AZURE_MONITOR_CONNECTION_STRING") or ""

KB_FIELDS_CONTENT = os.environ.get("KB_FIELDS_CONTENT") or "content"
KB_FIELDS_CATEGORY = os.environ.get("KB_FIELDS_CATEGORY") or "category"
Expand Down Expand Up @@ -71,6 +79,25 @@
KB_FIELDS_CONTENT)
}

# Set up OpenTelemetry tracing
trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer(__name__)

# Set up Azure Monitor Trace Exporter
azure_exporter = AzureMonitorTraceExporter.from_connection_string(AZURE_MONITOR_CONNECTION_STRING)
span_processor = BatchSpanProcessor(azure_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)

# Set up logging
logging.basicConfig(level=logging.ERROR,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
RotatingFileHandler("app.log", maxBytes=100000, backupCount=10),
logging.StreamHandler()
])
logger = logging.getLogger(__name__)


app = Flask(__name__)

@app.route("/", defaults={"path": "index.html"})
Expand All @@ -96,41 +123,42 @@ def content_file(path):

@app.route("/ask", methods=["POST"])
def ask():
ensure_openai_token()
if not request.json:
return jsonify({"error": "request must be json"}), 400
approach = request.json["approach"]
try:
impl = ask_approaches.get(approach)
if not impl:
return jsonify({"error": "unknown approach"}), 400
r = impl.run(request.json["question"], request.json.get("overrides") or {})
return jsonify(r)
except Exception as e:
logging.exception("Exception in /ask")
return jsonify({"error": str(e)}), 500

with tracer.start_as_current_span("ask"):
ensure_openai_token()
approach = request.json["approach"]
try:
impl = ask_approaches.get(approach)
if not impl:
app.logger.error("Unknown approach")
return jsonify({"error": "unknown approach"}), 400
r = impl.run(request.json["question"], request.json.get("overrides") or {})
return jsonify(r)
except Exception as e:
app.logger.error("Exception in /ask", exc_info=True)
return jsonify({"error": str(e)}), 500
@app.route("/chat", methods=["POST"])
def chat():
ensure_openai_token()
if not request.json:
return jsonify({"error": "request must be json"}), 400
approach = request.json["approach"]
try:
impl = chat_approaches.get(approach)
if not impl:
return jsonify({"error": "unknown approach"}), 400
r = impl.run(request.json["history"], request.json.get("overrides") or {})
return jsonify(r)
except Exception as e:
logging.exception("Exception in /chat")
return jsonify({"error": str(e)}), 500

with tracer.start_as_current_span("chat"):
ensure_openai_token()
approach = request.json["approach"]
try:
impl = chat_approaches.get(approach)
if not impl:
app.logger.error("Unknown approach")
return jsonify({"error": "unknown approach"}), 400
r = impl.run(request.json["history"], request.json.get("overrides") or {})
return jsonify(r)
except Exception as e:
app.logger.error("Exception in /chat", exc_info=True)
return jsonify({"error": str(e)}), 500
def ensure_openai_token():
global openai_token
if openai_token.expires_on < int(time.time()) - 60:
openai_token = azure_credential.get_token("https://cognitiveservices.azure.com/.default")
openai.api_key = openai_token.token

if __name__ == "__main__":
app.logger.setLevel(logging.INFO)
app.run()
53 changes: 44 additions & 9 deletions app/backend/approaches/chatreadretrieveread.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,49 @@
from typing import Any, Sequence

import os
import logging
from logging.handlers import RotatingFileHandler
import openai
import tiktoken
from azure.search.documents import SearchClient
from azure.search.documents.models import QueryType
from approaches.approach import Approach
from text import nonewlines
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleExportSpanProcessor
from azure.monitor.opentelemetry.exporter import AzureMonitorTraceExporter

from core.messagebuilder import MessageBuilder
from core.modelhelper import get_token_limit

AZURE_MONITOR_CONNECTION_STRING = os.environ.get("AZURE_MONITOR_CONNECTION_STRING") or ""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please set AZURE_MONITOR_CONNECTION_STRING in infra/main.bicep


# Set up Azure Monitor Trace Exporter
azure_exporter = AzureMonitorTraceExporter(
connection_string=AZURE_MONITOR_CONNECTION_STRING
)

# Set the tracer_provider to the SDK's TracerProvider
trace.set_tracer_provider(TracerProvider())

# Configure the tracer_provider with the Azure exporter
trace.get_tracer_provider().add_span_processor(
SimpleExportSpanProcessor(azure_exporter)
)
# Get the tracer
tracer = trace.get_tracer(__name__)

# Now you can use `tracer.start_as_current_span` to create spans

# Set up logging
logging.basicConfig(level=logging.ERROR,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
RotatingFileHandler("app.log", maxBytes=100000, backupCount=10),
logging.StreamHandler()
])
logger = logging.getLogger(__name__)

class ChatReadRetrieveReadApproach(Approach):
# Chat roles
SYSTEM = "system"
Expand Down Expand Up @@ -58,14 +92,15 @@ def __init__(self, search_client: SearchClient, chatgpt_deployment: str, chatgpt
self.chatgpt_token_limit = get_token_limit(chatgpt_model)

def run(self, history: Sequence[dict[str, str]], overrides: dict[str, Any]) -> Any:
has_text = overrides.get("retrieval_mode") in ["text", "hybrid", None]
has_vector = overrides.get("retrieval_mode") in ["vectors", "hybrid", None]
use_semantic_captions = True if overrides.get("semantic_captions") and has_text else False
top = overrides.get("top") or 3
exclude_category = overrides.get("exclude_category") or None
filter = "category ne '{}'".format(exclude_category.replace("'", "''")) if exclude_category else None
with tracer.start_as_current_span("ChatReadRetrieveReadApproach.run"):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an approach that doesnt require adding "with tracer" to each of these functions? Ideally, we could just attach middleware to the routes.

has_text = overrides.get("retrieval_mode") in ["text", "hybrid", None]
has_vector = overrides.get("retrieval_mode") in ["vectors", "hybrid", None]
use_semantic_captions = True if overrides.get("semantic_captions") and has_text else False
top = overrides.get("top") or 3
exclude_category = overrides.get("exclude_category") or None
filter = "category ne '{}'".format(exclude_category.replace("'", "''")) if exclude_category else None

user_q = 'Generate search query for: ' + history[-1]["user"]
user_q = 'Generate search query for: ' + history[-1]["user"]

# STEP 1: Generate an optimized keyword search query based on the chat history and the last question
messages = self.get_messages_from_history(
Expand Down Expand Up @@ -181,4 +216,4 @@ def get_messages_from_history(self, system_prompt: str, model_id: str, history:
break

messages = message_builder.messages
return messages
return messages
6 changes: 6 additions & 0 deletions app/backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ openai[datalib]==0.27.8
tiktoken==0.4.0
azure-search-documents==11.4.0b6
azure-storage-blob==12.14.1
azure-monitor-opentelemetry --pre
azure-monitor-opentelemetry==1.0.0b13
opentelemetry-instrumentation-requests
opentelemetry-instrumentation-flask
opentelemetry-instrumentation-wsgi
opentelemetry-sdk==1.17.0