First off, thanks for taking the time to contribute! ❤️
The best ways to contribute to Langfuse:
We welcome contributions through GitHub pull requests. This document outlines our conventions regarding development workflow, commit message formatting, contact points, and other resources. Our goal is to simplify the process and ensure that your contributions are easily accepted.
We gratefully welcome improvements to documentation (docs repo), the core application (this repo) and the SDKs (Python, JS).
The maintainers are available on Discord in case you have any questions.
And if you like the project, but just don't have time to contribute code, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
Before making any significant changes, please open an issue. Discussing your proposed changes ahead of time will make the contribution process smooth for everyone. Large changes that were not discussed in an issue may be rejected.
Once we've discussed your changes and you've got your code ready, make sure that tests are passing and open your pull request.
A good first step is to search for open issues. Issues are labeled, and some good issues to start with are labeled: good first issue.
- Application (this repository)
- NextJS 14, pages router
- NextAuth.js / Auth.js
- tRPC: Frontend APIs
- Prisma ORM
- Zod
- Tailwind CSS
- shadcn/ui tailwind components (using Radix and tanstack)
- Fern: generate OpenAPI spec and Pydantic models
- JS SDK (langfuse/langfuse-js)
- openapi-typescript to generated types based on OpenAPI spec
- Python SDK (langfuse/langfuse-python)
- Pydantic for input validation, models generated by fern
Note
Infrastructure will change in Langfuse version 3.0. More in the GitHub Discussions.
langfuse/langfuse/worker
is under active development and not recommended for production use in Langfuse 2.x
flowchart TB
subgraph s4["Clients"]
subgraph s2["langfuse/langfuse-python"]
Python["Python low-level SDK"]
Decorator["observe() decorator"] -->|extends| Python
OAI["OpenAI drop-in replacement"] -->|extends| Python
Llamaindex["LlamaIndex Integration"] -->|extends| Python
LCPYTHON["Langchain Python Integration"] -->|extends| Python
Langflow -->|uses| LCPYTHON
LiteLLM -->|uses| Python
end
subgraph s3["langfuse/langfuse-js"]
JS["JS SDK"]
LCJS["Langchain JS Integration"] -->|extends| JS
Flowise -->|uses| LCJS
end
end
DB[Postgres Database]
Redis[Redis]
subgraph s1["Application (langfuse/langfuse/web)"]
API[Public HTTP API]
G[TRPC API]
I[NextAuth]
H[React Frontend]
Prisma[Prisma ORM]
H --> G
H --> I
G --> I
G --- Prisma
API --- Prisma
I --- Prisma
end
subgraph s5["Application (langfuse/langfuse/worker)"]
Worker_API[Public HTTP API]
end
API --> Worker_API
Worker_API --- DB
Worker_API --- Redis
Prisma --- DB
JS --- API
Python --- API
Note
This will change in Langfuse version 3.0. More in the GitHub Discussions.
flowchart LR
Browser ---|Web UI & TRPC API| App
Integrations/SDKs ---|Public HTTP API| App
subgraph i1["Application Network"]
App["Langfuse Application (Docker or Serverless)"]
end
subgraph i2["Database Network"]
DB["Postgres Database"]
end
App --- DB
The diagram below may not show all relationships if the foreign key is not defined in the database schema. For instance, trace_id
in the observation
table is not defined as a foreign key to the trace
table to allow unordered ingestion of these objects, but it is still a foreign key in the application code.
Full database schema: packages/shared/prisma/schema.prisma
We built a monorepo using pnpm and turbo to manage the dependencies and build process. The monorepo contains the following packages:
web
: is the main application package providing Frontend and Backend APIs for Langfuse.worker
(no production yet): contains an application for asynchronous processing of tasks. This package is not yet used in production.packages
:shared
: contains shared code between the above packages.config-eslint
: contains eslint configurations which are shared between the above packages.config-typescript
: contains typescript configurations which are shared between the above packages.
ee
: contains all enterprise features. See EE README for more details.
Requirements
- Node.js 20 as specified in the .nvmrc
- Docker to run the database locally
Steps
-
Fork the repository and clone it locally
-
Run the development database
pnpm run infra:dev:up
-
Create an env file
cp .env.dev.example .env
-
Install dependencies
pnpm install
-
Run the migrations
All database migrations and configs are in the
shared
package.pnpm --filter=shared run db:migrate # Optional: seed the database # pnpm run db:seed # pnpm run db:seed:examples # pnpm --filter=shared run db:seed:load
-
Start the development server
pnpm run dev
-
Available packages and their dependencies
Packages are included in the monorepo according to the
pnpm-workspace.yaml
file. Each package maintains its own dependencies defined in thepackage.json
. Internal dependencies can be added as well by adding them to the package dependencies:"@langfuse/shared": "workspace:*"
. -
Executing commands
You can run commands in all packages at once. For example, to install all dependencies in all packages, you can execute:
pnpm install pnpm run dev pnpm --filter=web run dev # execute command only in one package
In the root
package.json
, you can find scripts which are executed with turbo e.g.turbo run dev
. These scripts are executed with the help of Turbo. Turbo executes the commands in all packages taking care of the correct order of execution. Task definitions can be found in theturbo.config.js
file. -
Run migrations
To run migrations, you can execute the following command.
pnpm run db:migrate -- --name <name of the migration>
Note
If you frequently switch branches, use pnpm run dx
instead of pnpm run dev
. This command will install dependencies, reset the database (wipe and apply all migrations), and run the database seeder with example data before starting the development server.
Note
If you find yourself stuck and want to clean the repo, execute pnpm run nuke
. It will remove all node_modules and build files.
- the ingestion API takes different event types (creation and updates of traces, generations, spans, events)
- The API loops through each event and:
- validates the event
- stores the event raw in the events table
- calculates tokens for
generations
- matches models from the
models
table to model forgenerations
events - upserts the event in the
traces
orobservations
table
- returns a
207
HTTP status code with a list of errors if any event failed to be ingested
On the main branch, we adhere to the best practices of conventional commits. All pull requests and branches are squash-merged to maintain a clean and readable history. This approach ensures the addition of a conventional commit message when merging contributions.
The API is tested using Jest. With the development server running, you can run the tests with:
Run all
npm run test
Run interactively in watch mode
npm run test:watch
These tests are also run in CI.
We use GitHub Actions for CI/CD, the configuration is in .github/workflows/pipeline.yml
CI on main
and pull_request
- Check Linting
- E2E test of API using Jest
- E2E tests of UI using Playwright
CD on main
- Publish Docker image to GitHub Packages if CI passes. Done on every push to
main
branch. Only released versions are tagged withlatest
.
We run a staging environment at https://staging.langfuse.com that is automatically deployed on every push to main
branch.
The same environment is also used for preview deployments of pull requests. Limitations:
- SSO is not available as dynamic domains are not supported by most SSO providers.
- When making changes to the database, migrations to the staging database need to be applied manually by a maintainer. If you want to interactively test database changes in the staging environment, please reach out.
You can use the staging environment end-to-end with the Langfuse integrations or SDKs (host: https://staging.langfuse.com
). However, please note that the staging environment is not intended for production use and may be reset at any time.
When a new release is tagged on the main
branch (excluding prereleases), it triggers a production deployment. The deployment process consists of two steps:
- The Docker image is published to GitHub Packages with the version number and
latest
tag. - The deployment is carried out on Langfuse Cloud. This is done by force pushing the
main
branch to theproduction
branch during every release, using therelease.yml
GitHub Action.
When applying changes to non-local environments, you may need to use secrets stored in 1Password. We use the 1Password CLI for this purpose.
Example:
op run --env-file="./.env" -- pnpm --filter=shared run db:deploy
Langfuse is MIT licensed, except for ee/
folder. See LICENSE and docs for more details.
When contributing to the Langfuse codebase, you need to agree to the Contributor License Agreement. You only need to do this once and the CLA bot will remind you if you haven't signed it yet.