Skip to content

Commit a8d9704

Browse files
openai: adds examples and flattens tests (elastic#46)
* openai: adds examples and flattens tests This adds an examples directory which include two main features this instrumentation supports: chat and embeddings It also flattens test recordings and infrastructure around unit tests. Notably, instrumentation code doesn't change depending on if azure is used or not. Unit testing only openai (platform) simplifies tests and reduces infrastructure significantly. Signed-off-by: Adrian Cole <[email protected]> * Add missing license headers * Apply suggestions from code review * prune Signed-off-by: Adrian Cole <[email protected]> * Introduce ollama after using OpenAI --------- Signed-off-by: Adrian Cole <[email protected]> Co-authored-by: Riccardo Magliocchetti <[email protected]>
1 parent 2f5a31d commit a8d9704

File tree

97 files changed

+1403
-15670
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+1403
-15670
lines changed

instrumentation/elastic-opentelemetry-instrumentation-openai/README.md

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,32 +22,49 @@ pip install elastic-opentelemetry-instrumentation-openai
2222

2323
This instrumentation supports *zero-code* / *autoinstrumentation*:
2424

25+
Set up a virtual environment with this package, the dependencies it requires
26+
and `dotenv` (a portable way to load environment variables).
27+
```
28+
python3 -m venv .venv
29+
source .venv/bin/activate
30+
pip install -r test-requirements.txt
31+
pip install python-dotenv[cli]
32+
```
33+
34+
Create a `.env` file containing the OpenAI API key:
35+
36+
```
37+
echo "OPENAI_API_KEY=sk-..." > .env
2538
```
26-
opentelemetry-instrument python use_openai.py
2739

28-
# You can record more information about prompts as log events by enabling content capture.
29-
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true opentelemetry-instrument python use_openai.py
40+
Run the script with telemetry setup to use the instrumentation.
41+
42+
```
43+
dotenv run -- opentelemetry-instrument python examples/chat.py
3044
```
3145

32-
Or manual instrumentation:
46+
You can record more information about prompts as log events by enabling content capture.
47+
```
48+
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true dotenv run -- \
49+
opentelemetry-instrument python examples/chat.py
50+
```
3351

34-
```python
35-
import openai
36-
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
52+
### Using a local model
3753

38-
OpenAIInstrumentor().instrument()
54+
[Ollama](https://ollama.com/) may be used to run examples without a cloud account. After you have set it up
55+
need to install the models in order to run the examples:
3956

40-
# assumes at least the OPENAI_API_KEY environment variable set
41-
client = openai.Client()
57+
```
58+
# for chat
59+
ollama pull qwen2.5:0.5b
60+
# for embeddings
61+
ollama pull all-minilm:33m
62+
```
4263

43-
messages = [
44-
{
45-
"role": "user",
46-
"content": "Answer in up to 3 words: Which ocean contains the canarian islands?",
47-
}
48-
]
64+
Finally run the examples using [ollama.env](ollama.env) variables to point to Ollama instead of OpenAI:
4965

50-
chat_completion = client.chat.completions.create(model="gpt-4o-mini", messages=messages)
66+
```
67+
dotenv run -f ollama.env -- opentelemetry-instrument python examples/chat.py
5168
```
5269

5370
### Instrumentation specific environment variable configuration
@@ -110,20 +127,22 @@ response without querying the LLM.
110127

111128
### Azure OpenAI Environment Variables
112129

113-
Azure is different from OpenAI primarily in that a URL has an implicit model. This means it ignores
114-
the model parameter set by the OpenAI SDK. The implication is that one endpoint cannot serve both
115-
chat and embeddings at the same time. Hence, we need separate environment variables for chat and
116-
embeddings. In either case, the `DEPLOYMENT_URL` is the "Endpoint Target URI" and the `API_KEY` is
117-
the `Endpoint Key` for a corresponding deployment in https://oai.azure.com/resource/deployments
118-
119-
* `AZURE_CHAT_COMPLETIONS_DEPLOYMENT_URL`
120-
* It should look like https://endpoint.com/openai/deployments/my-deployment/chat/completions?api-version=2023-05-15
121-
* `AZURE_CHAT_COMPLETIONS_API_KEY`
122-
* It should be in hex like `abc01...` and possibly the same as `AZURE_EMBEDDINGS_API_KEY`
123-
* `AZURE_EMBEDDINGS_DEPLOYMENT_URL`
124-
* It should look like https://endpoint.com/openai/deployments/my-deployment/embeddings?api-version=2023-05-15
125-
* `AZURE_EMBEDDINGS_API_KEY`
126-
* It should be in hex like `abc01...` and possibly the same as `AZURE_CHAT_COMPLETIONS_API_KEY`
130+
The `AzureOpenAI` client extends `OpenAI` with parameters specific to the Azure OpenAI Service.
131+
132+
* `AZURE_OPENAI_ENDPOINT` - "Azure OpenAI Endpoint" in https://oai.azure.com/resource/overview
133+
* It should look like `https://<your-resource-name>.openai.azure.com/`
134+
* `AZURE_OPENAI_API_KEY` - "API key 1 (or 2)" in https://oai.azure.com/resource/overview
135+
* It should look be a hex string like `abc01...`
136+
* `OPENAI_API_VERSION` = "Inference version" from https://learn.microsoft.com/en-us/azure/ai-services/openai/api-version-deprecation
137+
* It should look like `2024-10-01-preview`
138+
* `TEST_CHAT_MODEL` = "Name" from https://oai.azure.com/resource/deployments that deployed a model
139+
that supports tool calling, such as "gpt-4o-mini".
140+
* `TEST_EMBEDDINGS_MODEL` = "Name" from https://oai.azure.com/resource/deployments that deployed a
141+
model that supports embeddings, such as "text-embedding-3-small".
142+
143+
Note: The model parameter of a chat completion or embeddings request is substituted for an identical
144+
deployment name. As deployment names are arbitrary they may have no correlation with a real model
145+
like `gpt-4o`
127146

128147
## License
129148

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
# or more contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright
4+
# ownership. Elasticsearch B.V. licenses this file to you under
5+
# the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
import os
18+
19+
import openai
20+
21+
CHAT_MODEL = os.environ.get("TEST_CHAT_MODEL", "gpt-4o-mini")
22+
23+
24+
def main():
25+
client = openai.Client()
26+
27+
messages = [
28+
{
29+
"role": "user",
30+
"content": "Answer in up to 3 words: Which ocean contains Bouvet Island?",
31+
}
32+
]
33+
34+
chat_completion = client.chat.completions.create(model=CHAT_MODEL, messages=messages)
35+
print(chat_completion.choices[0].message.content)
36+
37+
38+
if __name__ == "__main__":
39+
main()
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
# or more contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright
4+
# ownership. Elasticsearch B.V. licenses this file to you under
5+
# the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
import os
18+
19+
import numpy as np
20+
import openai
21+
22+
EMBEDDINGS_MODEL = os.environ.get("TEST_EMBEDDINGS_MODEL", "text-embedding-3-small")
23+
24+
25+
def main():
26+
client = openai.Client()
27+
28+
products = [
29+
"Search: Ingest your data, and explore Elastic's machine learning and retrieval augmented generation (RAG) capabilities."
30+
"Observability: Unify your logs, metrics, traces, and profiling at scale in a single platform.",
31+
"Security: Protect, investigate, and respond to cyber threats with AI-driven security analytics."
32+
"Elasticsearch: Distributed, RESTful search and analytics.",
33+
"Kibana: Visualize your data. Navigate the Stack.",
34+
"Beats: Collect, parse, and ship in a lightweight fashion.",
35+
"Connectors: Connect popular databases, file systems, collaboration tools, and more.",
36+
"Logstash: Ingest, transform, enrich, and output.",
37+
]
38+
39+
# Generate embeddings for each product. Keep them in an array instead of a vector DB.
40+
product_embeddings = []
41+
for product in products:
42+
product_embeddings.append(create_embedding(client, product))
43+
44+
query_embedding = create_embedding(client, "What can help me connect to a database?")
45+
46+
# Calculate cosine similarity between the query and document embeddings
47+
similarities = []
48+
for product_embedding in product_embeddings:
49+
similarity = np.dot(query_embedding, product_embedding) / (
50+
np.linalg.norm(query_embedding) * np.linalg.norm(product_embedding)
51+
)
52+
similarities.append(similarity)
53+
54+
# Get the index of the most similar document
55+
most_similar_index = np.argmax(similarities)
56+
57+
print(products[most_similar_index])
58+
59+
60+
def create_embedding(client, text):
61+
return client.embeddings.create(input=[text], model=EMBEDDINGS_MODEL, encoding_format="float").data[0].embedding
62+
63+
64+
if __name__ == "__main__":
65+
main()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Env to run the integration tests against a local Ollama.
2+
OPENAI_BASE_URL=http://127.0.0.1:11434/v1
3+
OPENAI_API_KEY=notused
4+
5+
# These models may be substituted in the future with inexpensive to run, newer
6+
# variants.
7+
TEST_CHAT_MODEL=qwen2.5:0.5b
8+
TEST_EMBEDDINGS_MODEL=all-minilm:33m
9+
10+
OTEL_SERVICE_NAME=elastic-opentelemetry-instrumentation-openai

0 commit comments

Comments
 (0)