Skip to content

Commit

Permalink
split langfuse deployment automation into separate script
Browse files Browse the repository at this point in the history
  • Loading branch information
ahau-square committed Oct 7, 2024
1 parent 048b4fc commit 1174aba
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 95 deletions.
133 changes: 38 additions & 95 deletions packages/exchange/src/exchange/langfuse/langfuse.py
Original file line number Diff line number Diff line change
@@ -1,115 +1,60 @@
"""
Langfuse Integration Module
This module provides integration with Langfuse, a tool for monitoring and tracing LLM applications.
Usage:
Import this module to enable Langfuse integration.
It will automatically check for Langfuse credentials in the .env.langfuse.local file and for a running Langfuse server.
If these are found, it will set up the necessary client and context for tracing.
Note:
Run setup_langfuse.sh which automates the steps for running local Langfuse, a prerequisite for this Langfuse integration.
"""

import os
import logging
from typing import Callable
from dotenv import load_dotenv
import subprocess
import time
import platform
import socket
from langfuse.decorators import langfuse_context
import sys
from io import StringIO

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
if not logger.handlers:
handler = logging.StreamHandler()
handler.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)

HAS_LANGFUSE_CREDENTIALS = False
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
LANGFUSE_REPO_URL = "https://github.com/langfuse/langfuse.git"
LANGFUSE_CLONE_DIR = os.path.join(SCRIPT_DIR, "langfuse_clone")
LANGFUSE_LOCAL_ENV_FILE_NAME = ".env.langfuse.local"
LANGFUSE_LOCAL_INIT_ENV_FILE = os.path.join(SCRIPT_DIR, LANGFUSE_LOCAL_ENV_FILE_NAME)

HAS_LANGFUSE_CREDENTIALS = False

# Temporarily redirect stdout and stderr to suppress print statements from Langfuse
temp_stderr = StringIO()
sys.stderr = temp_stderr

def _run_command(command):
"""Run a shell command."""
result = subprocess.run(command, shell=True, check=True, text=True)
return result


def _update_or_clone_repo():
"""Update or clone the Langfuse repository."""
if os.path.isdir(os.path.join(LANGFUSE_CLONE_DIR, ".git")):
_run_command(f"git -C {LANGFUSE_CLONE_DIR} pull --rebase")
else:
_run_command(f"git clone {LANGFUSE_REPO_URL} {LANGFUSE_CLONE_DIR}")


def _copy_env_file():
"""Copy the environment file to the Langfuse clone directory."""
if os.path.isfile(LANGFUSE_LOCAL_INIT_ENV_FILE):
subprocess.run(["cp", LANGFUSE_LOCAL_INIT_ENV_FILE, LANGFUSE_CLONE_DIR], check=True)
else:
logger.error("Environment file not found. Exiting.")
exit(1)


def _start_docker_compose():
"""Start Docker containers for Langfuse service and Postgres db."""
_run_command(f"cd {LANGFUSE_CLONE_DIR} && docker compose --env-file ./{LANGFUSE_LOCAL_ENV_FILE_NAME} up --detach")


def _wait_for_service():
"""Wait for the Langfuse service to be available on localhost:3000."""
while True:
try:
with socket.create_connection(("localhost", 3000), timeout=1):
break
except OSError:
time.sleep(1)


def _launch_browser():
"""Launch the default web browser to open the Langfuse service URL."""
system = platform.system().lower()
url = "http://localhost:3000"

if "linux" in system:
subprocess.run(["xdg-open", url])
elif "darwin" in system: # macOS
subprocess.run(["open", url])
else:
logger.info("Please open http://localhost:3000 to view Langfuse traces.")


def _print_login_variables():
"""Read and print the default email and password from the environment file."""
if os.path.isfile(LANGFUSE_LOCAL_INIT_ENV_FILE):
print("Please log in with the initialization environment variables:")
env_vars = {}
with open(LANGFUSE_LOCAL_INIT_ENV_FILE) as f:
for line in f:
if "=" in line:
key, value = line.strip().split("=", 1)
env_vars[key] = value
print(f"Email: {env_vars.get('LANGFUSE_INIT_USER_EMAIL', 'Not found')}")
print(f"Password: {env_vars.get('LANGFUSE_INIT_USER_PASSWORD', 'Not found')}")
else:
logger.warning("Langfuse environment file with local credentials required for local login not found.")


def setup_langfuse():
"""Main function to set up and run the Langfuse service."""
try:
_update_or_clone_repo()
_copy_env_file()
_start_docker_compose()
_wait_for_service()
_launch_browser()
_print_login_variables()
load_dotenv(LANGFUSE_LOCAL_INIT_ENV_FILE)
if langfuse_context.auth_check():
logger.info(f"Langfuse credentials found. Find traces, if enabled, under {os.environ['LANGFUSE_HOST']}.")
global HAS_LANGFUSE_CREDENTIALS
HAS_LANGFUSE_CREDENTIALS = True
except Exception as e:
logger.warning(f"Trouble finding Langfuse or Langfuse credentials: {e}")
load_dotenv(LANGFUSE_LOCAL_INIT_ENV_FILE)
if langfuse_context.auth_check():
HAS_LANGFUSE_CREDENTIALS = True
logger.info("Langfuse context and credentials found.")
else:
logger.warning("Langfuse context and credentials not found. Langfuse will not work.")

# Restore stderr
sys.stderr = sys.__stderr__


def observe_wrapper(*args, **kwargs) -> Callable:
"""
A decorator that wraps a function with Langfuse context observation if credentials are available.
If Langfuse credentials are found, the function will be wrapped with Langfuse's observe method.
If Langfuse credentials were found, the function will be wrapped with Langfuse's observe method.
Otherwise, the function will be returned as-is.
Args:
Expand All @@ -127,5 +72,3 @@ def _wrapper(fn: Callable) -> Callable:
return fn

return _wrapper

setup_langfuse()
85 changes: 85 additions & 0 deletions packages/exchange/src/exchange/langfuse/setup_langfuse.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/bin/bash

# setup_langfuse.sh
#
# This script sets up and runs Langfuse locally for development and testing purposes.
#
# Key functionalities:
# 1. Clones or updates the Langfuse repository
# 2. Copies the local environment file
# 3. Starts Langfuse using Docker Compose with default initialization variables.
# 4. Waits for the service to be available
# 5. Launches a browser to open the local Langfuse UI
# 6. Prints login credentials from the environment file
#
# Usage:
# ./setup_langfuse.sh
#
# Requirements:
# - Git
# - Docker and Docker Compose
# - A .env.langfuse.local file in the same directory as this script. You may modify .env.langfuse.local to update default initialization credentials.
#
# Note: This script is intended for local development use only.


set -e

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
LANGFUSE_REPO_URL="https://github.com/langfuse/langfuse.git"
LANGFUSE_CLONE_DIR="$SCRIPT_DIR/langfuse_clone"
LANGFUSE_LOCAL_ENV_FILE_NAME=".env.langfuse.local"
LANGFUSE_LOCAL_INIT_ENV_FILE="$SCRIPT_DIR/$LANGFUSE_LOCAL_ENV_FILE_NAME"

update_or_clone_repo() {
if [ -d "$LANGFUSE_CLONE_DIR/.git" ]; then
git -C "$LANGFUSE_CLONE_DIR" pull --rebase
else
git clone "$LANGFUSE_REPO_URL" "$LANGFUSE_CLONE_DIR"
fi
}

copy_env_file() {
if [ -f "$LANGFUSE_LOCAL_INIT_ENV_FILE" ]; then
cp "$LANGFUSE_LOCAL_INIT_ENV_FILE" "$LANGFUSE_CLONE_DIR"
else
echo "Environment file not found. Exiting."
exit 1
fi
}

start_docker_compose() {
cd "$LANGFUSE_CLONE_DIR" && docker compose --env-file "./$LANGFUSE_LOCAL_ENV_FILE_NAME" up --detach
}

wait_for_service() {
while ! nc -z localhost 3000; do
sleep 1
done
}

launch_browser() {
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
xdg-open "http://localhost:3000"
elif [[ "$OSTYPE" == "darwin"* ]]; then
open "http://localhost:3000"
else
echo "Please open http://localhost:3000 to view Langfuse traces."
fi
}

print_login_variables() {
if [ -f "$LANGFUSE_LOCAL_INIT_ENV_FILE" ]; then
echo "Please log in with the initialization environment variables:"
grep -E "LANGFUSE_INIT_USER_EMAIL|LANGFUSE_INIT_USER_PASSWORD" "$LANGFUSE_LOCAL_INIT_ENV_FILE"
else
echo "Langfuse environment file with local credentials required for local login not found."
fi
}

update_or_clone_repo
copy_env_file
start_docker_compose
wait_for_service
launch_browser
print_login_variables
1 change: 1 addition & 0 deletions src/goose/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from goose.utils.autocomplete import SUPPORTED_SHELLS, setup_autocomplete
from goose.utils.session_file import list_sorted_session_files


@click.group()
def goose_cli() -> None:
pass
Expand Down

0 comments on commit 1174aba

Please sign in to comment.