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

Revert "Revert "Logfire Integration"" #3756

Merged
merged 1 commit into from
May 21, 2024
Merged
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
13 changes: 7 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
pip install langchain
pip install lunary==0.2.5
pip install "langfuse==2.27.1"
pip install "logfire==0.29.0"
pip install numpydoc
pip install traceloop-sdk==0.18.2
pip install openai
Expand Down Expand Up @@ -88,15 +89,14 @@ jobs:
exit 1
fi
cd ..


# Run pytest and generate JUnit XML report
- run:
name: Run tests
command: |
pwd
ls
python -m pytest -vv litellm/tests/ -x --junitxml=test-results/junit.xml --durations=5
python -m pytest -vv litellm/tests/ -x --junitxml=test-results/junit.xml --durations=5
no_output_timeout: 120m

# Store test results
Expand Down Expand Up @@ -172,6 +172,7 @@ jobs:
pip install "aioboto3==12.3.0"
pip install langchain
pip install "langfuse>=2.0.0"
pip install "logfire==0.29.0"
pip install numpydoc
pip install prisma
pip install fastapi
Expand Down Expand Up @@ -224,15 +225,15 @@ jobs:
name: Start outputting logs
command: docker logs -f my-app
background: true
- run:
- run:
name: Wait for app to be ready
command: dockerize -wait http://localhost:4000 -timeout 5m
- run:
name: Run tests
command: |
pwd
ls
python -m pytest -vv tests/ -x --junitxml=test-results/junit.xml --durations=5
python -m pytest -vv tests/ -x --junitxml=test-results/junit.xml --durations=5
no_output_timeout: 120m

# Store test results
Expand All @@ -254,7 +255,7 @@ jobs:
name: Copy model_prices_and_context_window File to model_prices_and_context_window_backup
command: |
cp model_prices_and_context_window.json litellm/model_prices_and_context_window_backup.json

- run:
name: Check if litellm dir was updated or if pyproject.toml was modified
command: |
Expand Down Expand Up @@ -339,4 +340,4 @@ workflows:
filters:
branches:
only:
- main
- main
60 changes: 60 additions & 0 deletions docs/my-website/docs/observability/logfire_integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Image from '@theme/IdealImage';

# Logfire - Logging LLM Input/Output

Logfire is open Source Observability & Analytics for LLM Apps
Detailed production traces and a granular view on quality, cost and latency

<Image img={require('../../img/logfire.png')} />

:::info
We want to learn how we can make the callbacks better! Meet the LiteLLM [founders](https://calendly.com/d/4mp-gd3-k5k/berriai-1-1-onboarding-litellm-hosted-version) or
join our [discord](https://discord.gg/wuPM9dRgDw)
:::

## Pre-Requisites

Ensure you have run `pip install logfire` for this integration

```shell
pip install logfire litellm
```

## Quick Start

Get your Logfire token from [Logfire](https://logfire.pydantic.dev/)

```python
litellm.success_callback = ["logfire"]
litellm.failure_callback = ["logfire"] # logs errors to logfire
```

```python
# pip install logfire
import litellm
import os

# from https://logfire.pydantic.dev/
os.environ["LOGFIRE_TOKEN"] = ""

# LLM API Keys
os.environ['OPENAI_API_KEY']=""

# set logfire as a callback, litellm will send the data to logfire
litellm.success_callback = ["logfire"]

# openai call
response = litellm.completion(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": "Hi 👋 - i'm openai"}
]
)
```

## Support & Talk to Founders

- [Schedule Demo 👋](https://calendly.com/d/4mp-gd3-k5k/berriai-1-1-onboarding-litellm-hosted-version)
- [Community Discord 💭](https://discord.gg/wuPM9dRgDw)
- Our numbers 📞 +1 (770) 8783-106 / ‭+1 (412) 618-6238‬
- Our emails ✉️ ishaan@berri.ai / krrish@berri.ai
Binary file added docs/my-website/img/logfire.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
178 changes: 178 additions & 0 deletions litellm/integrations/logfire_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#### What this does ####
# On success + failure, log events to Logfire

import dotenv, os

dotenv.load_dotenv() # Loading env variables using dotenv
import traceback
import uuid
from litellm._logging import print_verbose, verbose_logger

from enum import Enum
from typing import Any, Dict, NamedTuple
from typing_extensions import LiteralString


class SpanConfig(NamedTuple):
message_template: LiteralString
span_data: Dict[str, Any]


class LogfireLevel(str, Enum):
INFO = "info"
ERROR = "error"


class LogfireLogger:
# Class variables or attributes
def __init__(self):
try:
verbose_logger.debug(f"in init logfire logger")
import logfire

# only setting up logfire if we are sending to logfire
# in testing, we don't want to send to logfire
if logfire.DEFAULT_LOGFIRE_INSTANCE.config.send_to_logfire:
logfire.configure(token=os.getenv("LOGFIRE_TOKEN"))
except Exception as e:
print_verbose(f"Got exception on init logfire client {str(e)}")
raise e

def _get_span_config(self, payload) -> SpanConfig:
if (
payload["call_type"] == "completion"
or payload["call_type"] == "acompletion"
):
return SpanConfig(
message_template="Chat Completion with {request_data[model]!r}",
span_data={"request_data": payload},
)
elif (
payload["call_type"] == "embedding" or payload["call_type"] == "aembedding"
):
return SpanConfig(
message_template="Embedding Creation with {request_data[model]!r}",
span_data={"request_data": payload},
)
elif (
payload["call_type"] == "image_generation"
or payload["call_type"] == "aimage_generation"
):
return SpanConfig(
message_template="Image Generation with {request_data[model]!r}",
span_data={"request_data": payload},
)
else:
return SpanConfig(
message_template="Litellm Call with {request_data[model]!r}",
span_data={"request_data": payload},
)

async def _async_log_event(
self,
kwargs,
response_obj,
start_time,
end_time,
print_verbose,
level: LogfireLevel,
):
self.log_event(
kwargs=kwargs,
response_obj=response_obj,
start_time=start_time,
end_time=end_time,
print_verbose=print_verbose,
level=level,
)

def log_event(
self,
kwargs,
start_time,
end_time,
print_verbose,
level: LogfireLevel,
response_obj,
):
try:
import logfire

verbose_logger.debug(
f"logfire Logging - Enters logging function for model {kwargs}"
)

if not response_obj:
response_obj = {}
litellm_params = kwargs.get("litellm_params", {})
metadata = (
litellm_params.get("metadata", {}) or {}
) # if litellm_params['metadata'] == None
messages = kwargs.get("messages")
optional_params = kwargs.get("optional_params", {})
call_type = kwargs.get("call_type", "completion")
cache_hit = kwargs.get("cache_hit", False)
usage = response_obj.get("usage", {})
id = response_obj.get("id", str(uuid.uuid4()))
try:
response_time = (end_time - start_time).total_seconds()
except:
response_time = None

# Clean Metadata before logging - never log raw metadata
# the raw metadata can contain circular references which leads to infinite recursion
# we clean out all extra litellm metadata params before logging
clean_metadata = {}
if isinstance(metadata, dict):
for key, value in metadata.items():
# clean litellm metadata before logging
if key in [
"endpoint",
"caching_groups",
"previous_models",
]:
continue
else:
clean_metadata[key] = value

# Build the initial payload
payload = {
"id": id,
"call_type": call_type,
"cache_hit": cache_hit,
"startTime": start_time,
"endTime": end_time,
"responseTime (seconds)": response_time,
"model": kwargs.get("model", ""),
"user": kwargs.get("user", ""),
"modelParameters": optional_params,
"spend": kwargs.get("response_cost", 0),
"messages": messages,
"response": response_obj,
"usage": usage,
"metadata": clean_metadata,
}
logfire_openai = logfire.with_settings(custom_scope_suffix="openai")
message_template, span_data = self._get_span_config(payload)
if level == LogfireLevel.INFO:
logfire_openai.info(
message_template,
**span_data,
)
elif level == LogfireLevel.ERROR:
logfire_openai.error(
message_template,
**span_data,
_exc_info=True,
)
print_verbose(f"\ndd Logger - Logging payload = {payload}")

print_verbose(
f"Logfire Layer Logging - final response object: {response_obj}"
)
except Exception as e:
traceback.print_exc()
verbose_logger.debug(
f"Logfire Layer Error - {str(e)}\n{traceback.format_exc()}"
)
pass
Loading