Skip to content

Commit

Permalink
Introduce OpenAI example (#226)
Browse files Browse the repository at this point in the history
Signed-off-by: Adrian Cole <[email protected]>
  • Loading branch information
codefromthecrypt authored Jan 8, 2025
1 parent 2ca2ef9 commit f21c0dc
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ coverage
.eggs
.cache
/testdb.sql
.venv
venv
benchmarks/result*
coverage.xml
Expand Down
77 changes: 77 additions & 0 deletions examples/openai/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# OpenAI Zero-Code Instrumentation Examples

This is an example of how to instrument OpenAI calls with zero code changes,
using `opentelemetry-instrument` included in the Elastic Distribution of
OpenTelemetry Python ([EDOT Python][edot-python]).

When OpenAI examples run, they export traces, metrics and logs to an OTLP
compatible endpoint. Traces and metrics include details such as the model used
and the duration of the LLM request. In the case of chat, Logs capture the
request and the generated response. The combination of these provide a
comprehensive view of the performance and behavior of your OpenAI usage.

## Install

First, set up a Python virtual environment like this:
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

Next, install [EDOT Python][edot-python] and dotenv which is a portable way to
load environment variables.
```bash
pip install "python-dotenv[cli]" elastic-opentelemetry
```

Finally, run `edot-bootstrap` which analyzes the code to add relevant
instrumentation, to record traces, metrics and logs.
```bash
edot-bootstrap --action=install
```

## Configure

Copy [env.example](env.example) to `.env` and update its `OPENAI_API_KEY`.

An OTLP compatible endpoint should be listening for traces, metrics and logs on
`http://localhost:4317`. If not, update `OTEL_EXPORTER_OTLP_ENDPOINT` as well.

For example, if Elastic APM server is running locally, edit `.env` like this:
```
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8200
```

## Run

There are two examples, and they run the same way:

### Chat

[chat.py](chat.py) asks the LLM a geography question and prints the response.

Run it like this:
```bash
dotenv run -- opentelemetry-instrument python chat.py
```

You should see something like "Atlantic Ocean" unless your LLM hallucinates!

### Embeddings


[embeddings.py](embeddings.py) creates in-memory VectorDB embeddings about
Elastic products. Then, it searches for one similar to a question.

Run it like this:
```bash
dotenv run -- opentelemetry-instrument python embeddings.py
```

You should see something like "Connectors can help you connect to a database",
unless your LLM hallucinates!

---

[edot-python]: https://github.com/elastic/elastic-otel-python/blob/main/docs/get-started.md
39 changes: 39 additions & 0 deletions examples/openai/chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os

import openai

CHAT_MODEL = os.environ.get("CHAT_MODEL", "gpt-4o-mini")


def main():
client = openai.Client()

messages = [
{
"role": "user",
"content": "Answer in up to 3 words: Which ocean contains Bouvet Island?",
}
]

chat_completion = client.chat.completions.create(model=CHAT_MODEL, messages=messages)
print(chat_completion.choices[0].message.content)


if __name__ == "__main__":
main()
65 changes: 65 additions & 0 deletions examples/openai/embeddings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os

import numpy as np
import openai

EMBEDDINGS_MODEL = os.environ.get("EMBEDDINGS_MODEL", "text-embedding-3-small")


def main():
client = openai.Client()

products = [
"Search: Ingest your data, and explore Elastic's machine learning and retrieval augmented generation (RAG) capabilities."
"Observability: Unify your logs, metrics, traces, and profiling at scale in a single platform.",
"Security: Protect, investigate, and respond to cyber threats with AI-driven security analytics."
"Elasticsearch: Distributed, RESTful search and analytics.",
"Kibana: Visualize your data. Navigate the Stack.",
"Beats: Collect, parse, and ship in a lightweight fashion.",
"Connectors: Connect popular databases, file systems, collaboration tools, and more.",
"Logstash: Ingest, transform, enrich, and output.",
]

# Generate embeddings for each product. Keep them in an array instead of a vector DB.
product_embeddings = []
for product in products:
product_embeddings.append(create_embedding(client, product))

query_embedding = create_embedding(client, "What can help me connect to a database?")

# Calculate cosine similarity between the query and document embeddings
similarities = []
for product_embedding in product_embeddings:
similarity = np.dot(query_embedding, product_embedding) / (
np.linalg.norm(query_embedding) * np.linalg.norm(product_embedding)
)
similarities.append(similarity)

# Get the index of the most similar document
most_similar_index = np.argmax(similarities)

print(products[most_similar_index])


def create_embedding(client, text):
return client.embeddings.create(input=[text], model=EMBEDDINGS_MODEL, encoding_format="float").data[0].embedding


if __name__ == "__main__":
main()
26 changes: 26 additions & 0 deletions examples/openai/env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Update this with your real OpenAI API key
OPENAI_API_KEY=sk-YOUR_API_KEY

# Uncomment to use Ollama instead of OpenAI
# OPENAI_BASE_URL=http://localhost:11434/v1
# OPENAI_API_KEY=unused
# CHAT_MODEL=qwen2.5:0.5b
# EMBEDDINGS_MODEL=all-minilm:33m

# OTEL_EXPORTER_* variables are not required. If you would like to change your
# OTLP endpoint to Elastic APM server using HTTP, uncomment the following:
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8200
# OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

OTEL_SERVICE_NAME=openai-example

# Change to 'false' to hide prompt and completion content
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
# Change to affect behavior of which resources are detected. Note: these
# choices are specific to the language, in this case Python.
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS=process_runtime,os,otel,telemetry_distro

# Export metrics every 3 seconds instead of every minute
OTEL_METRIC_EXPORT_INTERVAL=3000
# Export traces every 3 seconds instead of every 5 seconds
OTEL_BSP_SCHEDULE_DELAY=3000
2 changes: 2 additions & 0 deletions examples/openai/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
openai~=1.59.4
numpy~=2.2.1

0 comments on commit f21c0dc

Please sign in to comment.