@percepta/create 3.1.2 → 3.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/dist/{chunk-CG7IJSB4.js → chunk-CO3YWUD6.js} +2 -2
- package/dist/{chunk-WMJT7CB5.js → chunk-V5EJIUBJ.js} +5 -2
- package/dist/index.js +21 -53
- package/dist/{init-XDWSYHYK.js → init-EQZ2TCSJ.js} +2 -2
- package/dist/{status-BTHGN6QH.js → status-QW5TQDYY.js} +1 -1
- package/dist/{sync-3Q27L7XZ.js → sync-RLBZDOFB.js} +1 -1
- package/dist/{upstream-C5KFAHVR.js → upstream-TQFVPMEG.js} +1 -1
- package/package.json +1 -1
- package/templates/monorepo/.dockerignore +18 -0
- package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +6 -2
- package/templates/webapp/.github/workflows/__APP_NAME__-terraform-ryvn-release.yaml +98 -0
- package/templates/webapp/AGENTS.md +17 -5
- package/templates/webapp/Dockerfile +16 -7
- package/templates/webapp/README.md +64 -2
- package/templates/webapp/agent-skills/deploy.md +48 -65
- package/templates/webapp/agent-skills/inngest.md +4 -4
- package/templates/webapp/agent-skills/langfuse.md +15 -14
- package/templates/webapp/agent-skills/llm.md +59 -0
- package/templates/webapp/agent-skills/oneshot.md +14 -1
- package/templates/webapp/agent-skills/ryvn.md +1 -1
- package/templates/webapp/deploy/README.md +34 -33
- package/templates/webapp/deploy/ryvn/__APP_NAME__-terraform.service.yaml +10 -0
- package/templates/webapp/deploy/ryvn/__APP_NAME__.service.yaml +2 -2
- package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml +11 -0
- package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +45 -9
- package/templates/webapp/env.example.template +20 -2
- package/templates/webapp/eslint.config.mjs +6 -0
- package/templates/webapp/next.config.ts +9 -0
- package/templates/webapp/package.json.template +6 -2
- package/templates/webapp/scripts/deploy-percepta-test.ts +837 -0
- package/templates/webapp/scripts/migrate.ts +3 -0
- package/templates/webapp/scripts/open-ryvn-deploy-pr.ts +5 -3
- package/templates/webapp/scripts/with-local-env.ts +75 -0
- package/templates/webapp/src/config/getEnvConfig.ts +14 -0
- package/templates/webapp/src/instrumentation.ts +102 -10
- package/templates/webapp/src/services/llm/LLMService.ts +88 -0
- package/templates/webapp/src/services/llm/LlmProviderService.ts +85 -0
- package/templates/webapp/terraform/schema/main.tf +4 -0
- package/templates/webapp/terraform/schema/outputs.tf +9 -0
- package/templates/webapp/terraform/schema/variables.tf +19 -0
- package/templates/webapp/terraform/schema/versions.tf +38 -0
- package/templates/webapp/.github/workflows/__APP_NAME__-terraform.yml +0 -28
|
@@ -9,7 +9,8 @@ A production-ready Next.js application with authentication, database, logging, b
|
|
|
9
9
|
- **Database** with PostgreSQL, Drizzle ORM, and migrations
|
|
10
10
|
- **Logging** with Pino and structured safe/unsafe data separation
|
|
11
11
|
- **Background Jobs** with Inngest
|
|
12
|
-
- **
|
|
12
|
+
- **LLM Calls** with a provider-backed `LLMService`
|
|
13
|
+
- **Observability** with OpenTelemetry, LGTM-compatible traces/metrics/logs, and Langfuse integration
|
|
13
14
|
- **Infrastructure** with Terraform modules for AWS (RDS, S3, IAM)
|
|
14
15
|
- **Type Safety** with TypeScript and Zod schemas
|
|
15
16
|
|
|
@@ -31,7 +32,15 @@ pnpm db:setup-and-migrate
|
|
|
31
32
|
|
|
32
33
|
Copy `.env.example` to `.env.local` and configure your environment variables.
|
|
33
34
|
|
|
34
|
-
### 4. Start
|
|
35
|
+
### 4. Start Inngest When Using Background Jobs
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm inngest:dev
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Run this in a separate terminal when the app has Inngest functions or you want the local Inngest dashboard.
|
|
42
|
+
|
|
43
|
+
### 5. Start Development Server
|
|
35
44
|
|
|
36
45
|
```bash
|
|
37
46
|
pnpm dev
|
|
@@ -39,6 +48,16 @@ pnpm dev
|
|
|
39
48
|
|
|
40
49
|
Open [http://localhost:3000](http://localhost:3000) to see your app.
|
|
41
50
|
|
|
51
|
+
OpenTelemetry, Faro, and Langfuse are optional in local development. Leave their env vars empty unless you are actively debugging telemetry; production deploys wire server traces, metrics, logs, and shared Langfuse demo credentials into the target Ryvn environment. General HTTP and database spans go to the OTEL/LGTM pipeline; Langfuse receives only AI SDK spans by default.
|
|
52
|
+
|
|
53
|
+
If you need local LLM calls, set provider keys once in your shell profile or in `~/.config/percepta/create.env`:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`pnpm dev` loads that shared file before starting Next.js, so you do not need to copy the same provider key into every generated app.
|
|
60
|
+
|
|
42
61
|
## Project Structure
|
|
43
62
|
|
|
44
63
|
```
|
|
@@ -53,6 +72,7 @@ src/
|
|
|
53
72
|
├── services/ # Business logic services
|
|
54
73
|
│ ├── inngest/ # Background job definitions
|
|
55
74
|
│ ├── langfuse/ # LLM observability
|
|
75
|
+
│ ├── llm/ # LLM provider selection and call helpers
|
|
56
76
|
│ └── logger/ # Structured logging
|
|
57
77
|
└── utils/ # Utility functions
|
|
58
78
|
```
|
|
@@ -67,6 +87,7 @@ src/
|
|
|
67
87
|
| `pnpm lint` | Run ESLint |
|
|
68
88
|
| `pnpm docker:up` | Start PostgreSQL container |
|
|
69
89
|
| `pnpm docker:down` | Stop PostgreSQL container |
|
|
90
|
+
| `pnpm inngest:dev` | Start the local Inngest dev server for this app |
|
|
70
91
|
| `pnpm db:generate` | Generate Drizzle migrations |
|
|
71
92
|
| `pnpm db:migrate` | Run database migrations |
|
|
72
93
|
| `pnpm db:setup` | Create database and user |
|
|
@@ -164,6 +185,47 @@ node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
|
|
|
164
185
|
| `LANGFUSE_PUBLIC_KEY` | Langfuse public key |
|
|
165
186
|
| `LANGFUSE_SECRET_KEY` | Langfuse secret key |
|
|
166
187
|
|
|
188
|
+
`deploy:percepta-test` sets the shared Langfuse URL and inherits the demo project keys from the `demos-commons` Ryvn variable group.
|
|
189
|
+
|
|
190
|
+
### LLM Providers
|
|
191
|
+
|
|
192
|
+
| Variable | Description |
|
|
193
|
+
|----------|-------------|
|
|
194
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key. Inherited from `demos-commons` for `percepta-test` deploys |
|
|
195
|
+
| `OPENAI_API_KEY` | OpenAI API key for local or non-demo deployments |
|
|
196
|
+
| `LLM_PROVIDER` | Optional provider override: `anthropic` or `openai` |
|
|
197
|
+
| `LLM_MODEL` | Optional model override |
|
|
198
|
+
|
|
199
|
+
Use `LLMService` for backend model calls:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { LLMService } from "@/services/llm/LLMService";
|
|
203
|
+
|
|
204
|
+
const result = await LLMService.create().generateText({
|
|
205
|
+
telemetryFunctionId: "summarize-note",
|
|
206
|
+
system: "You summarize notes for internal users.",
|
|
207
|
+
prompt: "Summarize this note...",
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
For local development, `pnpm dev` also loads `~/.config/percepta/create.env` when that file exists. Production deploys do not use that local file; they inherit provider keys from the target Ryvn environment.
|
|
212
|
+
|
|
213
|
+
### OpenTelemetry / LGTM
|
|
214
|
+
|
|
215
|
+
| Variable | Description |
|
|
216
|
+
|----------|-------------|
|
|
217
|
+
| `OTEL_SERVICE_NAME` | Service name attached to traces and metrics |
|
|
218
|
+
| `OTEL_RESOURCE_ATTRIBUTES` | Extra resource labels such as deployment environment |
|
|
219
|
+
| `OTEL_EXPORTER_OTLP_PROTOCOL` | OTLP protocol, usually `http/protobuf` |
|
|
220
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Base OTLP HTTP collector endpoint |
|
|
221
|
+
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Optional trace-specific OTLP endpoint |
|
|
222
|
+
| `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | Optional metric-specific OTLP endpoint |
|
|
223
|
+
| `OTEL_TRACES_EXPORTER` | Set to `otlp` to export server traces |
|
|
224
|
+
| `OTEL_METRICS_EXPORTER` | Set to `otlp` to export server metrics |
|
|
225
|
+
| `OTEL_METRIC_EXPORT_INTERVAL` | Metrics export interval in milliseconds |
|
|
226
|
+
|
|
227
|
+
`deploy:percepta-test` configures these for the existing percepta-test OTEL collector and LGTM stack. Application logs are written to stdout and collected by the platform collector.
|
|
228
|
+
|
|
167
229
|
## Local AWS Development
|
|
168
230
|
|
|
169
231
|
This application uses the default AWS SDK credential provider chain:
|
|
@@ -1,107 +1,90 @@
|
|
|
1
1
|
# Deploying to Percepta Test
|
|
2
2
|
|
|
3
|
-
This guide deploys __APP_TITLE__ to `https://__APP_NAME__.percepta-test.aitco.dev` using Ryvn. Tell Claude "deploy this app to percepta-test" and it
|
|
3
|
+
This guide deploys __APP_TITLE__ to `https://__APP_NAME__.percepta-test.aitco.dev` using Ryvn. Tell Claude "deploy this app to percepta-test" and it should run the direct deploy helper below.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This is the existing-environment deploy motion: `percepta-test` already owns the shared platform services, and this app is wired into them. Fresh-environment platform bootstrap is separate and should use a Ryvn blueprint or environment-specific platform rollout before app deploys run.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## What's Already Scaffolded
|
|
8
8
|
|
|
9
|
-
- `deploy/ryvn/__APP_NAME__.service.yaml` — Ryvn
|
|
10
|
-
- `deploy/ryvn/
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
- `deploy/ryvn/__APP_NAME__.service.yaml` — Ryvn server service for the web app.
|
|
10
|
+
- `deploy/ryvn/__APP_NAME__-terraform.service.yaml` — Ryvn Terraform service that creates the app's Postgres schema.
|
|
11
|
+
- `deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml` — web installation.
|
|
12
|
+
- `deploy/ryvn/environments/percepta-test/installations/__APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml` — schema installation.
|
|
13
|
+
- `.github/workflows/__APP_NAME__-ryvn-release.yaml` — builds the Docker image and creates the web Ryvn release.
|
|
14
|
+
- `.github/workflows/__APP_NAME__-terraform-ryvn-release.yaml` — creates the schema Terraform Ryvn release.
|
|
15
|
+
- `deploy/ryvn/percepta-test.secrets.env` — generated locally and ignored by git; patched into Ryvn by the deploy helper.
|
|
14
16
|
|
|
15
17
|
See [`deploy/README.md`](../deploy/README.md) for the file-by-file breakdown.
|
|
16
18
|
|
|
17
19
|
## Prerequisites
|
|
18
20
|
|
|
19
|
-
-
|
|
20
|
-
- The
|
|
21
|
-
- The
|
|
22
|
-
-
|
|
23
|
-
- The
|
|
21
|
+
- `git`, `gh`, and `ryvn` are installed and authenticated.
|
|
22
|
+
- The worktree is clean and committed. The helper pushes the current branch to `main` because GitHub Actions builds from GitHub.
|
|
23
|
+
- The Percepta-Core org has `RYVN_CLIENT_ID`, `RYVN_CLIENT_SECRET`, and `NPM_TOKEN` available as org-level GitHub secrets.
|
|
24
|
+
- These shared platform installations are already deployed and healthy in `percepta-test`: `percepta-internal-terraform`, `inngest-test`, `otel-collector`, `lgtm-stack-helm`, and `langfuse`.
|
|
25
|
+
- The `demos-commons` Ryvn variable group exists in `percepta-test` and provides `LANGFUSE_PUBLIC_KEY` plus sensitive `ANTHROPIC_API_KEY` and `LANGFUSE_SECRET_KEY` for shared demo LLM calls and Langfuse tracing.
|
|
24
26
|
|
|
25
27
|
## Deploy
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Run the generated deploy helper from this package directory:
|
|
29
|
+
From this package directory:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
pnpm deploy:percepta-test -- --
|
|
32
|
+
pnpm deploy:percepta-test -- --yes
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- uses `INFRA_REPO` or `--infra <path>` when provided
|
|
38
|
-
- otherwise uses a sibling `../infra` checkout, cloning `Percepta-Core/infra` there if needed
|
|
39
|
-
- copies the Ryvn service YAML into infra
|
|
40
|
-
- appends a `postgresql_schema "__APP_NAME_SNAKE__"` resource to `terraform/percepta-internal/databases.tf`
|
|
41
|
-
- opens the first PR against `Percepta-Core/infra`
|
|
42
|
-
|
|
43
|
-
Get the PR reviewed and merged.
|
|
44
|
-
|
|
45
|
-
### Step 2: Wait for service/schema import
|
|
35
|
+
The helper:
|
|
46
36
|
|
|
47
|
-
|
|
37
|
+
1. Checks the existing platform installations and shared demo variable group in `percepta-test`.
|
|
38
|
+
2. Creates `Percepta-Core/__REPO_NAME__` if needed.
|
|
39
|
+
3. Pushes the current branch to `main`.
|
|
40
|
+
4. Creates or replaces the Ryvn web and schema services.
|
|
41
|
+
5. Runs the schema Terraform release workflow.
|
|
42
|
+
6. Creates or replaces the schema installation and approves the Terraform plan.
|
|
43
|
+
7. Runs the web release workflow.
|
|
44
|
+
8. Creates or replaces the web installation.
|
|
45
|
+
9. Patches `BETTER_AUTH_SECRET` and `ENCRYPTION_SECRET_KEY` from `deploy/ryvn/percepta-test.secrets.env`.
|
|
46
|
+
10. Waits for Ryvn health and checks `/api/healthz`, `/api/readyz`, and the protected app route.
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Push to `main` in the app repo (or `gh workflow run "Build & Release __APP_NAME__"`). The release workflow builds the Docker image and creates a Ryvn release. Do this before creating the ServiceInstallation, otherwise GitOps can fail with `ReleaseNotFound`.
|
|
52
|
-
|
|
53
|
-
The workflow lives at `.github/workflows/__APP_NAME__-ryvn-release.yaml` (at the repo root, where GitHub Actions picks it up). It only fires on changes under `packages/__APP_NAME__/`, so unrelated edits to other packages in the monorepo won't trigger it.
|
|
54
|
-
|
|
55
|
-
### Step 4: Open the installation infra PR
|
|
48
|
+
The app will be available at **https://__APP_NAME__.percepta-test.aitco.dev**.
|
|
56
49
|
|
|
57
|
-
|
|
50
|
+
## Useful Variants
|
|
58
51
|
|
|
59
52
|
```bash
|
|
60
|
-
pnpm deploy:percepta-test -- --
|
|
53
|
+
pnpm deploy:percepta-test -- --skip-workflows --yes
|
|
54
|
+
pnpm deploy:percepta-test -- --skip-push --yes
|
|
55
|
+
pnpm deploy:percepta-test -- --timeout-minutes 30 --yes
|
|
61
56
|
```
|
|
62
57
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Get the PR reviewed and merged. Ryvn GitOps will import the ServiceInstallation and the Staging channel should deploy the latest release.
|
|
58
|
+
Use `--skip-workflows` when the required Ryvn releases already exist. Use `--skip-push` only when the target ref is already pushed.
|
|
66
59
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
After GitOps imports the installation, import this generated file in the Ryvn UI for the installation secrets:
|
|
60
|
+
The legacy infra-PR path is still available:
|
|
70
61
|
|
|
71
62
|
```bash
|
|
72
|
-
deploy
|
|
63
|
+
pnpm deploy:percepta-test:pr -- --phase service --yes
|
|
64
|
+
pnpm deploy:percepta-test:pr -- --phase installation --yes
|
|
73
65
|
```
|
|
74
66
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
### Step 6: Verify
|
|
67
|
+
## Verify
|
|
78
68
|
|
|
79
69
|
```bash
|
|
80
70
|
ryvn get installation __APP_NAME__ -e percepta-test
|
|
81
71
|
ryvn logs __APP_NAME__ -e percepta-test
|
|
82
72
|
curl -s https://__APP_NAME__.percepta-test.aitco.dev/api/healthz
|
|
83
73
|
curl -s https://__APP_NAME__.percepta-test.aitco.dev/api/readyz
|
|
74
|
+
curl -I https://__APP_NAME__.percepta-test.aitco.dev/
|
|
84
75
|
```
|
|
85
76
|
|
|
86
|
-
The app will be available at **https://__APP_NAME__.percepta-test.aitco.dev**.
|
|
87
|
-
|
|
88
77
|
## Troubleshooting
|
|
89
78
|
|
|
90
|
-
- **
|
|
79
|
+
- **Image build fails fetching @percepta packages** → check the Percepta-Core org-level `NPM_TOKEN` secret. Do not add a repo-level token unless the org secret is unavailable.
|
|
80
|
+
- **Ryvn release already exists** → commit a new change or re-run with `--skip-workflows` if the current releases are already present.
|
|
81
|
+
- **Terraform plan needs approval** → the helper approves it when run with `--yes`; without `--yes`, approve the prompt.
|
|
82
|
+
- **Auth/sign-in routes fail after install** → verify the deploy helper patched `BETTER_AUTH_SECRET` and `ENCRYPTION_SECRET_KEY`.
|
|
91
83
|
- **Pod crash-looping** → check `ryvn logs`; migration or database connectivity failures are the most common fresh-deploy causes.
|
|
92
|
-
- **Database
|
|
93
|
-
- **
|
|
94
|
-
- **
|
|
95
|
-
- **
|
|
84
|
+
- **Database schema missing** → check `ryvn get installation __APP_NAME__-terraform -e percepta-test`.
|
|
85
|
+
- **Inngest can't reach the app** → `INNGEST_APP_URL` must use the k8s service name `__APP_NAME__-web-server`.
|
|
86
|
+
- **Platform preflight fails** → deploy or repair the missing shared installation first. This helper only wires apps into an existing environment.
|
|
87
|
+
- **No Langfuse traces** → verify the target environment has Langfuse deployed and that the `demos-commons` variable group has `LANGFUSE_PUBLIC_KEY` and sensitive `LANGFUSE_SECRET_KEY`.
|
|
88
|
+
- **LLM calls fail after deploy** → verify `demos-commons` has sensitive `ANTHROPIC_API_KEY` and the installation has `LLM_PROVIDER=anthropic`.
|
|
96
89
|
|
|
97
90
|
For Ryvn CLI operations, use the `/use-ryvn` skill.
|
|
98
|
-
|
|
99
|
-
## Updating shared platform values
|
|
100
|
-
|
|
101
|
-
The Inngest and OTel URLs are baked as literals in every webapp's installation YAML. If percepta-test infra ever changes those endpoints, update them with a single grep across the infra repo:
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
grep -rl "inngest.percepta-test.svc.cluster.local:8288" ryvn/environments/percepta-test/installations/
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
A future improvement would be a shared ConfigMap or Ryvn output reference; for now the scaffolded literals are the convention.
|
|
@@ -102,13 +102,13 @@ await inngest.client.send({
|
|
|
102
102
|
|
|
103
103
|
## Running Inngest Locally
|
|
104
104
|
|
|
105
|
-
### 1.
|
|
105
|
+
### 1. Start the Inngest Dev Server
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
-
pnpm
|
|
108
|
+
pnpm inngest:dev
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
This starts the Inngest Dev Server
|
|
111
|
+
This starts the Inngest Dev Server for `http://localhost:3000/api/inngest` with a dashboard UI.
|
|
112
112
|
|
|
113
113
|
### 2. Set environment variables
|
|
114
114
|
|
|
@@ -126,7 +126,7 @@ INNGEST_EVENT_KEY=local # any value works locally
|
|
|
126
126
|
pnpm dev
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
The Inngest Dev Server auto-discovers functions by calling the serve endpoint at `/api/inngest`. Open `
|
|
129
|
+
The Inngest Dev Server auto-discovers functions by calling the serve endpoint at `/api/inngest`. Open the dashboard URL printed by `pnpm inngest:dev` to see registered functions, trigger events manually, and inspect runs.
|
|
130
130
|
|
|
131
131
|
## Environment Variables
|
|
132
132
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Langfuse is an open-source LLM observability platform. It captures traces, spans, and generations from LLM calls so you can debug prompt behavior, monitor latency/cost, and run evaluations. Think of it as "Datadog for LLM apps."
|
|
4
4
|
|
|
5
|
-
**Our opinionated setup:** We integrate Langfuse via OpenTelemetry (OTEL), not the Langfuse SDK directly. OTEL auto-instruments the app (HTTP, DB, etc.), and Langfuse
|
|
5
|
+
**Our opinionated setup:** We integrate Langfuse via OpenTelemetry (OTEL), not the Langfuse SDK directly. OTEL auto-instruments the app (HTTP, DB, etc.), exports general traces to the environment's OTEL collector, and adds Langfuse as a span processor when `LANGFUSE_*` values are configured. This gives us unified tracing while still letting the app run when Langfuse is not available in an environment.
|
|
6
6
|
|
|
7
7
|
## When to Use Langfuse
|
|
8
8
|
|
|
@@ -18,24 +18,28 @@ Langfuse is an open-source LLM observability platform. It captures traces, spans
|
|
|
18
18
|
|
|
19
19
|
### OpenTelemetry + Langfuse Span Processor
|
|
20
20
|
|
|
21
|
-
The template uses Next.js's instrumentation hook (called on server startup) to bootstrap OTEL with Langfuse:
|
|
21
|
+
The template uses Next.js's instrumentation hook (called on server startup) to bootstrap OTEL with both the environment collector and optional Langfuse:
|
|
22
22
|
|
|
23
23
|
```typescript
|
|
24
24
|
import { LangfuseSpanProcessor } from "@langfuse/otel";
|
|
25
|
-
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
25
|
+
import { NodeSDK, tracing } from "@opentelemetry/sdk-node";
|
|
26
26
|
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
27
27
|
|
|
28
28
|
const sdk = new NodeSDK({
|
|
29
|
-
spanProcessors: [
|
|
29
|
+
spanProcessors: [
|
|
30
|
+
new tracing.BatchSpanProcessor(otlpTraceExporter),
|
|
31
|
+
new LangfuseSpanProcessor({ baseUrl, publicKey, secretKey }),
|
|
32
|
+
],
|
|
30
33
|
instrumentations: [getNodeAutoInstrumentations()],
|
|
31
34
|
});
|
|
32
35
|
sdk.start();
|
|
33
36
|
```
|
|
34
37
|
|
|
35
38
|
- `getNodeAutoInstrumentations()` automatically instruments HTTP calls, database queries, and other standard Node.js operations.
|
|
36
|
-
-
|
|
39
|
+
- The OTLP span processor forwards general server traces to the environment collector.
|
|
40
|
+
- `LangfuseSpanProcessor` forwards only AI SDK spans to Langfuse when all `LANGFUSE_*` variables are set. General HTTP, DB, and server spans stay in the OTEL/LGTM pipeline and are not sent to Langfuse by default.
|
|
37
41
|
|
|
38
|
-
**You do not need to manually instrument LLM calls.**
|
|
42
|
+
**You do not need to manually instrument LLM calls.** Use `LLMService` from `src/services/llm/LLMService.ts`; it calls the Vercel AI SDK with telemetry enabled and provider/model metadata attached.
|
|
39
43
|
|
|
40
44
|
### LangfuseService (Direct API)
|
|
41
45
|
|
|
@@ -58,12 +62,9 @@ This is a singleton — `create()` returns the same instance every time. It grac
|
|
|
58
62
|
|
|
59
63
|
### Percepta's Internal Instance
|
|
60
64
|
|
|
61
|
-
Percepta runs a shared Langfuse instance.
|
|
65
|
+
Percepta runs a shared Langfuse instance. For `percepta-test` deploys, the generated Ryvn installation uses the shared demo project by inheriting `LANGFUSE_PUBLIC_KEY` and sensitive `LANGFUSE_SECRET_KEY` from the `demos-commons` variable group.
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
2. Log into the Langfuse dashboard
|
|
65
|
-
3. Go to **Settings → API Keys** and create a new key pair
|
|
66
|
-
4. You'll get a **public key** and **secret key**
|
|
67
|
+
For non-demo environments, verify the target Ryvn environment has a Langfuse installation or shared Langfuse project, then store the project keys in an environment-scoped variable group or installation secrets.
|
|
67
68
|
|
|
68
69
|
### Self-Hosted / Langfuse Cloud
|
|
69
70
|
|
|
@@ -75,10 +76,10 @@ For external projects, you can:
|
|
|
75
76
|
|
|
76
77
|
### Option 1: Use Percepta's Langfuse (recommended)
|
|
77
78
|
|
|
78
|
-
Set the keys in `.env.local`
|
|
79
|
+
Set the keys in `.env.local` if you want local traces to go to the shared instance:
|
|
79
80
|
|
|
80
81
|
```bash
|
|
81
|
-
LANGFUSE_BASE_URL=https://langfuse.
|
|
82
|
+
LANGFUSE_BASE_URL=https://langfuse.percepta-test.aitco.dev
|
|
82
83
|
LANGFUSE_PUBLIC_KEY=pk-lf-...
|
|
83
84
|
LANGFUSE_SECRET_KEY=sk-lf-...
|
|
84
85
|
```
|
|
@@ -104,7 +105,7 @@ LANGFUSE_SECRET_KEY=sk-lf-...
|
|
|
104
105
|
|
|
105
106
|
### Option 3: Skip Langfuse entirely
|
|
106
107
|
|
|
107
|
-
Simply don't set the `LANGFUSE_*` env vars. The instrumentation gracefully skips the Langfuse span processor, and `LangfuseService.isConfigured()` returns `false`. OTEL still instruments the app for general tracing.
|
|
108
|
+
Simply don't set the `LANGFUSE_*` env vars. This is the default local development path unless you are specifically debugging LLM telemetry. The instrumentation gracefully skips the Langfuse span processor, and `LangfuseService.isConfigured()` returns `false`. OTEL still instruments the app for general tracing when an OTLP collector endpoint is configured.
|
|
108
109
|
|
|
109
110
|
## Environment Variables
|
|
110
111
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# LLM Calls
|
|
2
|
+
|
|
3
|
+
Use this guide when the app needs backend model calls, streaming responses, or structured generation.
|
|
4
|
+
|
|
5
|
+
## Default Pattern
|
|
6
|
+
|
|
7
|
+
Use `LLMService` from `src/services/llm/LLMService.ts`. Do not instantiate provider clients directly in routes, tRPC routers, or Inngest functions.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { LLMService } from "@/services/llm/LLMService";
|
|
11
|
+
|
|
12
|
+
const result = await LLMService.create().generateText({
|
|
13
|
+
telemetryFunctionId: "summarize-document",
|
|
14
|
+
system: "You summarize documents for internal operators.",
|
|
15
|
+
prompt: `Summarize this document:\n\n${documentText}`,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return result.text;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For streaming:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
const stream = LLMService.create().streamText({
|
|
25
|
+
telemetryFunctionId: "chat-response",
|
|
26
|
+
system: "You are a concise assistant.",
|
|
27
|
+
messages,
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Providers
|
|
32
|
+
|
|
33
|
+
`LlmProviderService` chooses a provider at call time:
|
|
34
|
+
|
|
35
|
+
1. `LLM_PROVIDER`, when explicitly set.
|
|
36
|
+
2. Anthropic when `ANTHROPIC_API_KEY` is available.
|
|
37
|
+
3. OpenAI when `OPENAI_API_KEY` is available.
|
|
38
|
+
|
|
39
|
+
`LLM_MODEL` can override the default model. A per-call `modelId` or `provider` passed to `LLMService` overrides env defaults.
|
|
40
|
+
|
|
41
|
+
## Deployment
|
|
42
|
+
|
|
43
|
+
For `percepta-test`, `deploy:percepta-test` attaches the `demos-commons` Ryvn variable group. That group provides the shared `ANTHROPIC_API_KEY` and Langfuse project keys, so generated demo apps do not need per-app LLM secrets.
|
|
44
|
+
|
|
45
|
+
## Local Development
|
|
46
|
+
|
|
47
|
+
Local LLM calls are optional. If needed, set a provider key once in your shell profile or `~/.config/percepta/create.env`:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`pnpm dev` loads `~/.config/percepta/create.env` automatically. Do not put shared demo keys in committed files. Do not require local Langfuse or a local LGTM stack unless the task is specifically about telemetry.
|
|
54
|
+
|
|
55
|
+
## Observability
|
|
56
|
+
|
|
57
|
+
`LLMService` enables AI SDK telemetry by default and attaches provider/model metadata to each call. When `LANGFUSE_*` values are configured, the template's OpenTelemetry bootstrap sends LLM spans to Langfuse. In `percepta-test`, those values are inherited from the environment.
|
|
58
|
+
|
|
59
|
+
Use a stable `telemetryFunctionId` for every meaningful LLM operation, such as `extract-invoice-fields` or `draft-member-message`.
|
|
@@ -78,12 +78,16 @@ Cover:
|
|
|
78
78
|
2. **API routes** — which tRPC routers and procedures
|
|
79
79
|
3. **Pages** — which routes/pages in the app
|
|
80
80
|
4. **Background jobs** — which Inngest events and functions (if any)
|
|
81
|
-
5. **LLM integration** —
|
|
81
|
+
5. **LLM integration** — use `LLMService` from `src/services/llm/LLMService.ts` when model calls are needed
|
|
82
82
|
|
|
83
83
|
### Decision: Langfuse
|
|
84
84
|
|
|
85
85
|
Read [agent-skills/langfuse.md](langfuse.md) to understand Langfuse. **Use Langfuse if the app calls LLMs.** Skip it otherwise. The template already has the integration wired — you just need to ensure env vars are set.
|
|
86
86
|
|
|
87
|
+
### Decision: LLM Calls
|
|
88
|
+
|
|
89
|
+
Read [agent-skills/llm.md](llm.md) when the app calls a model. Use `LLMService` for backend model calls and set a stable `telemetryFunctionId` for each LLM operation. Do not create provider clients directly in app code.
|
|
90
|
+
|
|
87
91
|
### Decision: Inngest
|
|
88
92
|
|
|
89
93
|
Read [agent-skills/inngest.md](inngest.md) to understand Inngest. **Use Inngest if the app has background jobs, scheduled tasks, or multi-step agent pipelines.** The template has the scaffold — you add events and functions.
|
|
@@ -125,6 +129,7 @@ If it fails, fix the errors before moving to the next chunk. Do not accumulate b
|
|
|
125
129
|
- **Use robust loading states.** `@percepta/components` is optional; add it first if you want `AsyncContent`.
|
|
126
130
|
- **Use `getEnvConfig()` for env vars.** Never use `process.env` directly.
|
|
127
131
|
- **Use `getLogger()` for logging.** Never use `console.log`. Use safe/unsafe field separation.
|
|
132
|
+
- **Use `LLMService` for model calls.** It handles provider selection and Langfuse/OTEL telemetry metadata.
|
|
128
133
|
- **Follow the singleton pattern** for new services. Follow the existing `DatabaseService` singleton pattern in the codebase.
|
|
129
134
|
- **Use `protectedProcedure`** for authenticated tRPC routes.
|
|
130
135
|
|
|
@@ -141,6 +146,14 @@ pnpm docker:up
|
|
|
141
146
|
pnpm db:setup-and-migrate
|
|
142
147
|
```
|
|
143
148
|
|
|
149
|
+
If the app uses Inngest functions, start the local Inngest dev server in a separate terminal:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pnpm inngest:dev
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Do not start local LGTM, OTEL collector, Faro, or Langfuse services unless the task is specifically about telemetry. Those integrations are optional locally and are wired by the Ryvn environment for deploys.
|
|
156
|
+
|
|
144
157
|
### Step 2: Start the dev server
|
|
145
158
|
|
|
146
159
|
```bash
|
|
@@ -8,7 +8,7 @@ Ryvn is the deployment platform used by Percepta to manage cloud infrastructure.
|
|
|
8
8
|
|-------------|---------|---------------|
|
|
9
9
|
| `percepta-test` | Internal dev/test | `<app>.percepta-test.aitco.dev` |
|
|
10
10
|
|
|
11
|
-
New apps are deployed to **percepta-test
|
|
11
|
+
New apps are deployed to **percepta-test** using the existing-environment deploy motion. This environment must already have shared PostgreSQL, Inngest, OTEL collector, LGTM stack, and Langfuse installations before app deploys run. The service installation points at the shared Langfuse URL and attaches the `demos-commons` variable group for shared demo Langfuse project keys.
|
|
12
12
|
|
|
13
13
|
## Deploying This App
|
|
14
14
|
|
|
@@ -1,65 +1,66 @@
|
|
|
1
1
|
# Deploy
|
|
2
2
|
|
|
3
|
-
This directory holds infrastructure-as-code for deploying __APP_TITLE__ to Percepta environments.
|
|
3
|
+
This directory holds Ryvn infrastructure-as-code for deploying __APP_TITLE__ to Percepta environments.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
deploy/
|
|
7
7
|
└── ryvn/
|
|
8
|
-
├── __APP_NAME__.service.yaml
|
|
8
|
+
├── __APP_NAME__.service.yaml
|
|
9
|
+
├── __APP_NAME__-terraform.service.yaml
|
|
9
10
|
└── environments/
|
|
10
11
|
└── percepta-test/
|
|
11
12
|
└── installations/
|
|
12
|
-
|
|
13
|
+
├── __APP_NAME__.env.percepta-test.serviceinstallation.yaml
|
|
14
|
+
└── __APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
These files
|
|
17
|
+
These files deploy to `https://__APP_NAME__.percepta-test.aitco.dev`.
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Tell Claude "deploy this app to percepta-test" — it'll follow [`agent-skills/deploy.md`](../agent-skills/deploy.md), which runs the generated PR helper and then verifies Ryvn.
|
|
19
|
+
The default deploy helper performs the existing-environment deploy motion: it assumes the target Ryvn environment already has the shared platform services installed, then wires this app into them. For `percepta-test`, that means shared Postgres, Inngest, the OTEL collector, the LGTM stack, and Langfuse must already exist before app deploy starts. Fresh-environment platform bootstrap is a separate motion and should be handled by a Ryvn blueprint or environment-specific platform rollout.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
The helper talks directly to Ryvn: it preflights the existing platform services, creates/updates the services, runs the GitHub Actions release workflows, creates the schema installation, approves the schema Terraform plan, creates the web installation, patches generated secrets, waits for health, and verifies the health and app routes.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
## Deploying
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Tell Claude "deploy this app to percepta-test" or run:
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
```bash
|
|
28
|
+
pnpm deploy:percepta-test -- --yes
|
|
29
|
+
```
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
The helper expects a clean, committed git worktree because GitHub Actions builds from the pushed repo. It can create `Percepta-Core/__REPO_NAME__` if it does not exist yet, and it pushes the current branch to `main` before triggering releases.
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## What's in these files
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
**`__APP_NAME__.service.yaml`** — Ryvn server service for the web app. It points at `Percepta-Core/__REPO_NAME__` and builds from `packages/__APP_NAME__/`.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
**`__APP_NAME__-terraform.service.yaml`** — Ryvn Terraform service that owns the per-app Postgres schema in the shared `demos` database.
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
pnpm deploy:percepta-test -- --phase service --yes
|
|
41
|
-
```
|
|
39
|
+
**`__APP_NAME__.env.percepta-test.serviceinstallation.yaml`** — web app installation for `percepta-test`. It wires the shared platform services, ingress, health probes, database connection, Inngest, OTEL/LGTM telemetry, the Langfuse base URL, and the shared demo variable group.
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
**`__APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml`** — schema installation for `percepta-test`.
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
**`percepta-test.secrets.env`** — generated locally and ignored by git. The deploy helper patches app-specific auth/encryption secrets into the Ryvn installation; shared Langfuse and LLM demo keys are inherited from a Ryvn variable group.
|
|
46
44
|
|
|
47
|
-
|
|
45
|
+
## Platform Wiring
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
pnpm deploy:percepta-test -- --phase installation --yes
|
|
51
|
-
```
|
|
47
|
+
`pnpm deploy:percepta-test` checks these existing `percepta-test` installations before it mutates GitHub or Ryvn app resources:
|
|
52
48
|
|
|
53
|
-
|
|
49
|
+
- `percepta-internal-terraform` for the shared Postgres instance.
|
|
50
|
+
- `inngest-test` for background jobs and function callbacks.
|
|
51
|
+
- `otel-collector` for trace, metric, and log collection.
|
|
52
|
+
- `lgtm-stack-helm` for Loki, Grafana, Tempo, and Mimir.
|
|
53
|
+
- `langfuse` for LLM tracing and eval observability.
|
|
54
|
+
- `demos-commons` variable group for shared demo configuration, including the Anthropic API key and Langfuse demo project keys.
|
|
54
55
|
|
|
55
|
-
The
|
|
56
|
+
The service installation sets `LANGFUSE_BASE_URL` to the shared `percepta-test` Langfuse URL, sets `LLM_PROVIDER=anthropic`, and attaches `demos-commons` for `ANTHROPIC_API_KEY`, `LANGFUSE_PUBLIC_KEY`, and sensitive `LANGFUSE_SECRET_KEY`. Individual demo apps do not need LLM or Langfuse keys in `percepta-test.secrets.env`.
|
|
56
57
|
|
|
57
|
-
##
|
|
58
|
+
## Notes
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
The release workflows live at the repo root under `.github/workflows/`. The web workflow uses the org-level `NPM_TOKEN` secret for private `@percepta/*` packages; do not add a repo-level token unless the org secret is unavailable.
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
For the old GitOps PR path, use `pnpm deploy:percepta-test:pr -- --phase service --yes` and then `pnpm deploy:percepta-test:pr -- --phase installation --yes`.
|
|
62
63
|
|
|
63
|
-
## Adding
|
|
64
|
+
## Adding More Environments
|
|
64
65
|
|
|
65
|
-
Copy `
|
|
66
|
+
Copy the two `percepta-test` installation manifests to `environments/<env>/installations/`, then change the `environment:`, host, and environment-specific config.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
kind: ServiceInstallation
|
|
2
|
+
metadata:
|
|
3
|
+
name: __APP_NAME__-terraform
|
|
4
|
+
spec:
|
|
5
|
+
service: __APP_NAME__-terraform
|
|
6
|
+
environment: percepta-test
|
|
7
|
+
config: |
|
|
8
|
+
aws_region: {{ .ryvn.env.state.cluster_region }}
|
|
9
|
+
database_secret_name: {{ .ryvn.installations.percepta_internal_terraform.outputs.percepta_internal_secrets_manager_secret_name }}
|
|
10
|
+
database_name: demos
|
|
11
|
+
schema_name: __APP_NAME_SNAKE__
|