From 56a177c0e7b367caf8295c66d8aef61c8c53c2e3 Mon Sep 17 00:00:00 2001 From: shuningc Date: Wed, 4 Feb 2026 15:24:42 -0800 Subject: [PATCH] Updating zero code sets of credentials, readme and manifest --- .../examples/zero-code/.env.example | 60 +++++-- .../examples/zero-code/README.rst | 167 ++++++++++++++---- .../examples/zero-code/deployment.yaml | 39 ++-- .../examples/zero-code/requirements.txt | 7 + .../examples/zero-code/server.py | 154 +++++++++++----- 5 files changed, 328 insertions(+), 99 deletions(-) diff --git a/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/.env.example b/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/.env.example index bea2c27e..657e6f05 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/.env.example +++ b/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/.env.example @@ -1,15 +1,49 @@ -# Cisco CircuIT Configuration -CIRCUIT_BASE_URL=https://your-circuit-gateway.cisco.com/v1 -CIRCUIT_TOKEN_URL=https://your-circuit-gateway.cisco.com/oauth2/token -CIRCUIT_CLIENT_ID=your_client_id_here -CIRCUIT_CLIENT_SECRET=your_secret_here -CIRCUIT_APP_KEY=llamaindex-zero-code-demo -CIRCUIT_SCOPE=api.read - -# OpenTelemetry configuration -OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 +# Choose ONE auth mode: +# 1) OAuth2 gateway mode (set all required LLM_* fields), or +# 2) Standard OpenAI mode (set OPENAI_API_KEY only). + +# ============================================================================= +# OAuth2 gateway configuration (preferred for enterprise gateways) +# ============================================================================= +# Required for OAuth2 mode: +LLM_CLIENT_ID= +LLM_CLIENT_SECRET= +LLM_TOKEN_URL= +LLM_BASE_URL= +# Optional: +LLM_APP_KEY= +LLM_SCOPE= + +# ============================================================================= +# Standard OpenAI configuration (fallback mode) +# ============================================================================= +OPENAI_API_KEY=sk-... +OPENAI_MODEL_NAME=gpt-4o-mini + +# Service Identity OTEL_SERVICE_NAME=llamaindex-zero-code-example +OTEL_RESOURCE_ATTRIBUTES=deployment.environment=demo +# OTLP Exporter +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 +OTEL_EXPORTER_OTLP_PROTOCOL=grpc +# Logs +OTEL_LOGS_EXPORTER=otlp +OTEL_PYTHON_LOG_CORRELATION=true +OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true +# Metrics +OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta + +#Optional +DEEPEVAL_LLM_BASE_URL=https:///openai/deployments/ +DEEPEVAL_LLM_MODEL=gpt-4o-mini +DEEPEVAL_LLM_PROVIDER=openai +DEEPEVAL_LLM_CLIENT_ID= +DEEPEVAL_LLM_CLIENT_SECRET= +DEEPEVAL_LLM_TOKEN_URL=https:///oauth2/token +DEEPEVAL_LLM_CLIENT_APP_NAME= +DEEPEVAL_FILE_SYSTEM=READ_ONLY -# Instrumentation options -OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true -OTEL_INSTRUMENTATION_GENAI_EMITTERS=span_metric_event +#Debug settings +OTEL_INSTRUMENTATION_GENAI_DEBUG=false +OTEL_GENAI_EVAL_DEBUG_SKIPS=false +OTEL_GENAI_EVAL_DEBUG_EACH=false diff --git a/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/README.rst b/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/README.rst index 5220e675..e2078600 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/README.rst +++ b/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/README.rst @@ -5,7 +5,12 @@ This is an example of how to instrument LlamaIndex calls using zero-code instrumentation with :code:`opentelemetry-instrument`. This example provides an HTTP server (:code:`server.py`) that uses -LlamaIndex with Cisco CircuIT API. When run with the :code:`opentelemetry-instrument` +LlamaIndex with either: + +- an OAuth2-protected LLM gateway (via :code:`LLM_*` variables), or +- standard OpenAI API key auth (via :code:`OPENAI_API_KEY`). + +When run with the :code:`opentelemetry-instrument` command, it automatically exports traces, metrics, and events to an OTLP-compatible endpoint. @@ -16,7 +21,7 @@ The following packages are required: - :code:`llama-index>=0.14.0` - LlamaIndex framework - :code:`python-dotenv>=1.0.0` - Environment variable management -- :code:`requests>=2.31.0` - HTTP client for CircuIT API +- :code:`requests>=2.31.0` - HTTP client for OAuth2 gateway API - :code:`opentelemetry-distro` - OpenTelemetry distribution with auto-instrumentation - :code:`opentelemetry-exporter-otlp` - OTLP exporter for traces and metrics @@ -29,13 +34,25 @@ Install with: Environment Variables --------------------- -**Required:** +Choose one auth mode: + +**Mode A - OAuth2 Gateway (required fields):** + +- ``LLM_BASE_URL`` - LLM gateway chat completions endpoint +- ``LLM_TOKEN_URL`` - OAuth2 token endpoint +- ``LLM_CLIENT_ID`` - OAuth2 client ID +- ``LLM_CLIENT_SECRET`` - OAuth2 client secret + +**Mode A - OAuth2 Gateway (optional fields):** -- ``CIRCUIT_BASE_URL`` - CircuIT API endpoint (e.g., https://chat-ai.cisco.com/openai/deployments/gpt-4o-mini/chat/completions) -- ``CIRCUIT_TOKEN_URL`` - OAuth2 token endpoint (e.g., https://id.cisco.com/oauth2/default/v1/token) -- ``CIRCUIT_CLIENT_ID`` - CircuIT OAuth2 client ID -- ``CIRCUIT_CLIENT_SECRET`` - CircuIT OAuth2 client secret -- ``CIRCUIT_APP_KEY`` - CircuIT application key +- ``LLM_APP_KEY`` - app key header or request metadata key +- ``LLM_SCOPE`` - OAuth2 scope +- ``OPENAI_MODEL_NAME`` - model label used for metadata (default: ``gpt-4o-mini``) + +**Mode B - OpenAI API key:** + +- ``OPENAI_API_KEY`` - OpenAI API key +- ``OPENAI_MODEL_NAME`` - model name (default: ``gpt-4o-mini``) **OpenTelemetry Configuration:** @@ -73,15 +90,79 @@ Telemetry Data Setup ----- -1. **Create** a :code:`.env` file with your CircuIT credentials: +Set the required environment variables in the :code:`.env` file. + +For more information about AI app configuration, see: +`Configure the Python agent for AI applications `_. + +**LLM configuration credentials:** .. code-block:: console - CIRCUIT_BASE_URL=https://chat-ai.cisco.com/openai/deployments/gpt-4o-mini/chat/completions - CIRCUIT_TOKEN_URL=https://id.cisco.com/oauth2/default/v1/token - CIRCUIT_CLIENT_ID=your-client-id - CIRCUIT_CLIENT_SECRET=your-client-secret - CIRCUIT_APP_KEY=your-app-key + # OpenAI API Key + OPENAI_API_KEY=sk-YOUR_API_KEY + + # Or OAuth2 LLM Provider (for enterprise deployments) + LLM_CLIENT_ID= + LLM_CLIENT_SECRET= + LLM_TOKEN_URL=https:///oauth2/token + LLM_BASE_URL=https:///openai/deployments + LLM_APP_KEY= # Optional + LLM_SCOPE= # Optional + + # Example: Cisco CircuIT Configuration + LLM_BASE_URL=https://your-circuit-gateway.cisco.com/v1 + LLM_TOKEN_URL=https://your-circuit-gateway.cisco.com/oauth2/token + LLM_CLIENT_ID=your_client_id_here + LLM_CLIENT_SECRET=your_secret_here + LLM_APP_KEY=llamaindex-zero-code-demo + LLM_SCOPE=api.read + +**OpenTelemetry configuration settings:** + +.. code-block:: console + + # Service Identity + OTEL_SERVICE_NAME=llamaindex-zero-code-example + OTEL_RESOURCE_ATTRIBUTES=deployment.environment=demo + + # OTLP Exporter + OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 + OTEL_EXPORTER_OTLP_PROTOCOL=grpc + + # Logs + OTEL_LOGS_EXPORTER=otlp + OTEL_PYTHON_LOG_CORRELATION=true + OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true + + # Metrics + OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta + +**(Optional) GenAI evaluation settings and debug settings:** + +.. code-block:: console + + # DeepEval custom LLM-as-a-Judge settings + DEEPEVAL_LLM_BASE_URL=https:///openai/deployments/ + DEEPEVAL_LLM_MODEL=gpt-4o-mini + DEEPEVAL_LLM_PROVIDER=openai + DEEPEVAL_LLM_CLIENT_ID= + DEEPEVAL_LLM_CLIENT_SECRET= + DEEPEVAL_LLM_TOKEN_URL=https:///oauth2/token + DEEPEVAL_LLM_CLIENT_APP_NAME= + DEEPEVAL_FILE_SYSTEM=READ_ONLY + + # Debug settings + OTEL_INSTRUMENTATION_GENAI_DEBUG=false + OTEL_GENAI_EVAL_DEBUG_SKIPS=false + OTEL_GENAI_EVAL_DEBUG_EACH=false + +1. **Create** a :code:`.env` file with the settings above. Example: + + .. code-block:: console + + OPENAI_API_KEY=sk-... + OPENAI_MODEL_NAME=gpt-4o-mini 2. Set up a virtual environment: @@ -102,20 +183,19 @@ Setup Run Locally ----------- -1. Export environment variables: +1. Start the Splunk Distribution of the OpenTelemetry Collector (example using Docker): .. code-block:: console - export CIRCUIT_BASE_URL="https://chat-ai.cisco.com/openai/deployments/gpt-4o-mini/chat/completions" - export CIRCUIT_TOKEN_URL="https://id.cisco.com/oauth2/default/v1/token" - export CIRCUIT_CLIENT_ID="your-client-id" - export CIRCUIT_CLIENT_SECRET="your-client-secret" - export CIRCUIT_APP_KEY="your-app-key" - export OTEL_SERVICE_NAME="llamaindex-zero-code-server" - export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT="true" - export OTEL_INSTRUMENTATION_GENAI_EMITTERS="span_metric_event" + docker run -p 4317:4317 -p 4318:4318 otel/opentelemetry-collector -2. Run the server with zero-code instrumentation: +2. Load the environment variables from :code:`.env`: + + .. code-block:: console + + set -a && source .env && set +a + +3. Run the server with zero-code instrumentation: .. code-block:: console @@ -124,7 +204,7 @@ Run Locally --metrics_exporter otlp \ python server.py -3. The server will start on port 8080. Test it with curl: +4. Send a curl request to generate traces: .. code-block:: console @@ -132,7 +212,7 @@ Run Locally -H "Content-Type: application/json" \ -d '{"message": "What is 2+2?", "system_prompt": "You are a helpful assistant"}' -4. Check for traces and metrics in your observability backend. If you see warnings about +5. Check for traces and metrics in your observability backend. If you see warnings about connection failures, ensure your OpenTelemetry Collector is running on the configured endpoint. Docker @@ -149,11 +229,11 @@ To build and run with Docker: # Run with environment variables docker run -p 8080:8080 \ - -e CIRCUIT_BASE_URL=https://chat-ai.cisco.com/openai/deployments/gpt-4o-mini/chat/completions \ - -e CIRCUIT_TOKEN_URL=https://id.cisco.com/oauth2/default/v1/token \ - -e CIRCUIT_CLIENT_ID=your-client-id \ - -e CIRCUIT_CLIENT_SECRET=your-client-secret \ - -e CIRCUIT_APP_KEY=your-app-key \ + -e LLM_BASE_URL=https:///openai/deployments/gpt-4o-mini/chat/completions \ + -e LLM_TOKEN_URL=https:///oauth2/token \ + -e LLM_CLIENT_ID=your-client-id \ + -e LLM_CLIENT_SECRET=your-client-secret \ + -e LLM_APP_KEY=your-app-key \ -e OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \ -e OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true \ -e OTEL_INSTRUMENTATION_GENAI_EMITTERS=span_metric_event \ @@ -168,25 +248,40 @@ Kubernetes Deployment kubectl create namespace llamaindex-zero-code -2. Create the CircuIT credentials secret: +2. Create OAuth2 gateway secret (optional, if using OAuth2 mode): + + .. code-block:: console + + kubectl create secret generic llm-credentials \ + -n llamaindex-zero-code \ + --from-literal=base-url=https:///openai/deployments/gpt-4o-mini/chat/completions \ + --from-literal=token-url=https:///oauth2/token \ + --from-literal=client-id= \ + --from-literal=client-secret= \ + --from-literal=app-key= \ + --from-literal=scope= + +3. Create OpenAI secret (optional, if using OpenAI mode): .. code-block:: console - kubectl apply -f circuit-secret.yaml + kubectl create secret generic openai-credentials \ + -n llamaindex-zero-code \ + --from-literal=api-key=sk-... -3. Deploy the server: +4. Deploy the server: .. code-block:: console kubectl apply -f deployment.yaml -4. Deploy the cronjob client: +5. Deploy the cronjob client: .. code-block:: console kubectl apply -f cronjob.yaml -5. Verify the deployment: +6. Verify the deployment: .. code-block:: console diff --git a/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/deployment.yaml b/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/deployment.yaml index 0bdc109f..27ebbed3 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/deployment.yaml +++ b/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/deployment.yaml @@ -59,31 +59,50 @@ spec: name: llamaindex-server-config env: - - name: CIRCUIT_BASE_URL + - name: LLM_BASE_URL valueFrom: secretKeyRef: - name: circuit-credentials + name: llm-credentials key: base-url - - name: CIRCUIT_TOKEN_URL + optional: true + - name: LLM_TOKEN_URL valueFrom: secretKeyRef: - name: circuit-credentials + name: llm-credentials key: token-url - - name: CIRCUIT_CLIENT_ID + optional: true + - name: LLM_CLIENT_ID valueFrom: secretKeyRef: - name: circuit-credentials + name: llm-credentials key: client-id - - name: CIRCUIT_CLIENT_SECRET + optional: true + - name: LLM_CLIENT_SECRET valueFrom: secretKeyRef: - name: circuit-credentials + name: llm-credentials key: client-secret - - name: CIRCUIT_APP_KEY + optional: true + - name: LLM_APP_KEY valueFrom: secretKeyRef: - name: circuit-credentials + name: llm-credentials key: app-key + optional: true + - name: LLM_SCOPE + valueFrom: + secretKeyRef: + name: llm-credentials + key: scope + optional: true + - name: OPENAI_API_KEY + valueFrom: + secretKeyRef: + name: openai-credentials + key: api-key + optional: true + - name: OPENAI_MODEL_NAME + value: "gpt-4o-mini" - name: OTEL_SERVICE_NAME value: "llamaindex-zero-code-server" - name: OTEL_RESOURCE_ATTRIBUTES diff --git a/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/requirements.txt b/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/requirements.txt index f2c0a78e..eb5d8ab6 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/requirements.txt +++ b/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/requirements.txt @@ -1,3 +1,10 @@ llama-index>=0.14.0 python-dotenv>=1.0.0 requests>=2.31.0 +opentelemetry-distro +opentelemetry-exporter-otlp +splunk-otel-instrumentation-llamaindex +splunk-otel-util-genai +splunk-otel-genai-emitters-splunk +splunk-otel-util-genai-evals +splunk-otel-genai-evals-deepeval diff --git a/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/server.py b/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/server.py index 12b5f51a..0e9f2a5b 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/server.py +++ b/instrumentation-genai/opentelemetry-instrumentation-llamaindex/examples/zero-code/server.py @@ -1,27 +1,98 @@ """ -LlamaIndex Zero-Code Server using CircuIT LLM. +LlamaIndex Zero-Code Server using CircuIT or OpenAI LLM. This server exposes an HTTP endpoint for chat requests and uses zero-code OpenTelemetry instrumentation via opentelemetry-instrument. -Run with: opentelemetry-instrument python main_server.py +Run with: opentelemetry-instrument python server.py """ import json import os -import sys -from pathlib import Path +import base64 +import time from typing import Any from http.server import BaseHTTPRequestHandler, HTTPServer import requests -# Add parent directory to path to import from util -sys.path.insert(0, str(Path(__file__).parent.parent)) -from util import OAuth2TokenManager from llama_index.core.base.llms.types import ChatMessage, ChatResponse, MessageRole from llama_index.core.llms import CustomLLM, CompletionResponse, LLMMetadata from llama_index.core.llms.callbacks import llm_chat_callback +from llama_index.llms.openai import OpenAI + + +class OAuth2TokenManager: + """Simple OAuth2 client-credentials token manager for custom LLM gateways.""" + + def __init__( + self, + *, + token_url: str, + client_id: str, + client_secret: str, + scope: str | None = None, + token_refresh_buffer_seconds: int = 300, + ) -> None: + self.token_url = token_url + self.client_id = client_id + self.client_secret = client_secret + self.scope = scope + self.token_refresh_buffer = token_refresh_buffer_seconds + self._token: str | None = None + self._token_expiry = 0.0 + + def get_token(self) -> str: + if self._token and time.time() < ( + self._token_expiry - self.token_refresh_buffer + ): + return self._token + return self._refresh_token() + + def _refresh_token(self) -> str: + credentials = base64.b64encode( + f"{self.client_id}:{self.client_secret}".encode() + ).decode() + data = {"grant_type": "client_credentials"} + if self.scope: + data["scope"] = self.scope + response = requests.post( + self.token_url, + headers={ + "Accept": "*/*", + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": f"Basic {credentials}", + }, + data=data, + timeout=30, + ) + response.raise_for_status() + token_data = response.json() + self._token = str(token_data["access_token"]) + expires_in = int(token_data.get("expires_in", 3600)) + self._token_expiry = time.time() + expires_in + return self._token + + +# ============================================================================= +# LLM Configuration - OAuth2 Provider +# ============================================================================= + +# Optional app key for request tracking +LLM_APP_KEY = os.environ.get("LLM_APP_KEY") + +# Check if we should use OAuth2 or standard OpenAI +USE_OAUTH2 = bool(os.environ.get("LLM_CLIENT_ID")) + +# Initialize token manager if OAuth2 credentials are present +token_manager: OAuth2TokenManager | None = None +if USE_OAUTH2: + token_manager = OAuth2TokenManager( + token_url=os.environ.get("LLM_TOKEN_URL", ""), + client_id=os.environ.get("LLM_CLIENT_ID", ""), + client_secret=os.environ.get("LLM_CLIENT_SECRET", ""), + scope=os.environ.get("LLM_SCOPE"), + ) # Custom LLM for Cisco CircuIT @@ -30,7 +101,7 @@ class CircuITLLM(CustomLLM): api_url: str token_manager: OAuth2TokenManager - app_key: str + app_key: str | None = None model_name: str = "gpt-4o-mini" temperature: float = 0.0 @@ -58,8 +129,12 @@ def chat(self, messages: list[ChatMessage], **kwargs: Any) -> ChatResponse: } ) - # CircuIT requires appkey as JSON string in user field - user_field = json.dumps({"appkey": self.app_key}) + payload: dict[str, Any] = { + "messages": api_messages, + "temperature": self.temperature, + } + if self.app_key: + payload["user"] = json.dumps({"appkey": self.app_key}) # Make request to CircuIT response = requests.post( @@ -68,11 +143,7 @@ def chat(self, messages: list[ChatMessage], **kwargs: Any) -> ChatResponse: "api-key": access_token, "Content-Type": "application/json", }, - json={ - "messages": api_messages, - "temperature": self.temperature, - "user": user_field, - }, + json=payload, timeout=60, ) response.raise_for_status() @@ -106,32 +177,34 @@ def stream_complete(self, prompt: str, **kwargs: Any): # Initialize LLM def initialize_llm(): - """Initialize CircuIT LLM from environment variables.""" - circuit_base_url = os.getenv("CIRCUIT_BASE_URL") - circuit_token_url = os.getenv("CIRCUIT_TOKEN_URL") - circuit_client_id = os.getenv("CIRCUIT_CLIENT_ID") - circuit_client_secret = os.getenv("CIRCUIT_CLIENT_SECRET") - circuit_app_key = os.getenv("CIRCUIT_APP_KEY", "llamaindex-zero-code-demo") - circuit_scope = os.getenv("CIRCUIT_SCOPE") - - if not all( - [circuit_base_url, circuit_token_url, circuit_client_id, circuit_client_secret] - ): - raise RuntimeError("Missing required CircuIT environment variables") + """Initialize OAuth2 gateway LLM or fall back to standard OpenAI API key.""" + openai_model_name = os.getenv("OPENAI_MODEL_NAME", "gpt-4o-mini") + llm_base_url = os.getenv("LLM_BASE_URL") + # Backward-compatible fallback names + llm_base_url = llm_base_url or os.getenv("CIRCUIT_BASE_URL") + openai_api_key = os.getenv("OPENAI_API_KEY") + + if USE_OAUTH2 and token_manager: + if not llm_base_url: + raise RuntimeError( + "LLM_BASE_URL is required when using OAuth2 gateway credentials." + ) - token_manager = OAuth2TokenManager( - token_url=circuit_token_url, - client_id=circuit_client_id, - client_secret=circuit_client_secret, - scope=circuit_scope, - ) + return CircuITLLM( + api_url=str(llm_base_url), + token_manager=token_manager, + app_key=LLM_APP_KEY, + model_name=openai_model_name, + temperature=0.0, + ) + + if openai_api_key: + return OpenAI(model=openai_model_name, temperature=0.0) - return CircuITLLM( - api_url=circuit_base_url, - token_manager=token_manager, - app_key=circuit_app_key, - model_name="gpt-4o-mini", - temperature=0.0, + raise RuntimeError( + "No LLM credentials configured. Set either OAuth2 gateway credentials " + "(LLM_BASE_URL/LLM_TOKEN_URL/LLM_CLIENT_ID/LLM_CLIENT_SECRET) " + "or OPENAI_API_KEY." ) @@ -205,7 +278,8 @@ def run_server(port=8080): """Run the HTTP server.""" # Initialize LLM ChatRequestHandler.llm = initialize_llm() - print("✓ LlamaIndex CircuIT LLM initialized") + provider = "CircuIT" if isinstance(ChatRequestHandler.llm, CircuITLLM) else "OpenAI" + print(f"✓ LlamaIndex {provider} LLM initialized") server_address = ("", port) httpd = HTTPServer(server_address, ChatRequestHandler)