@percepta/create 3.1.2 → 3.1.4
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 +18 -6
- package/templates/webapp/Dockerfile +16 -7
- package/templates/webapp/README.md +65 -3
- package/templates/webapp/agent-skills/database.md +5 -1
- package/templates/webapp/agent-skills/deploy.md +49 -64
- package/templates/webapp/agent-skills/inngest.md +17 -12
- 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 +15 -2
- 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 +8 -4
- package/templates/webapp/scripts/deploy-percepta-test.ts +1112 -0
- package/templates/webapp/scripts/generate-migrations.ts +28 -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/drizzle/__tests__/migrationSql.test.ts +24 -0
- package/templates/webapp/src/drizzle/migrationSql.ts +8 -0
- package/templates/webapp/src/instrumentation.ts +102 -10
- package/templates/webapp/src/services/inngest/AppWorkflowService.ts +19 -0
- package/templates/webapp/src/services/inngest/__tests__/AppWorkflowService.test.ts +19 -0
- package/templates/webapp/src/services/inngest/events/AppEvents.ts +7 -13
- package/templates/webapp/src/services/inngest/events/payloads/ExampleEventPayload.ts +1 -3
- 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
|
```
|
|
@@ -65,8 +85,9 @@ src/
|
|
|
65
85
|
| `pnpm build` | Build for production |
|
|
66
86
|
| `pnpm start` | Start production server |
|
|
67
87
|
| `pnpm lint` | Run ESLint |
|
|
68
|
-
| `pnpm docker:up` | Start PostgreSQL container |
|
|
88
|
+
| `pnpm docker:up` | Start PostgreSQL container and wait until it is healthy |
|
|
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:
|
|
@@ -38,7 +38,11 @@ export * from "./documents";
|
|
|
38
38
|
pnpm db:generate
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
This creates a new SQL migration file
|
|
41
|
+
This creates a new SQL migration file and normalizes generated foreign key
|
|
42
|
+
references so they stay schema-relative when `DATABASE_SCHEMA` is set in
|
|
43
|
+
`percepta-test`. **Review the generated SQL** — Drizzle generates it
|
|
44
|
+
automatically but you should verify it's correct, especially for new foreign
|
|
45
|
+
keys and destructive changes.
|
|
42
46
|
|
|
43
47
|
### 4. Apply the migration
|
|
44
48
|
|
|
@@ -1,107 +1,92 @@
|
|
|
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; injected into the app installation as Ryvn secrets 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. Creates or updates app-scoped Ryvn installation secrets for `BETTER_AUTH_SECRET` and `ENCRYPTION_SECRET_KEY` from `deploy/ryvn/percepta-test.secrets.env`. On first install, the helper injects them into the create manifest so the first pod starts with auth configured.
|
|
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
|
-
|
|
77
|
+
For apps with tRPC routes, also verify at least one endpoint that initializes Better Auth or app services. `healthz` can be green even when app-specific secrets or workflow wiring are wrong.
|
|
87
78
|
|
|
88
79
|
## Troubleshooting
|
|
89
80
|
|
|
90
|
-
- **
|
|
81
|
+
- **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.
|
|
82
|
+
- **Ryvn release already exists** → commit a new change or re-run with `--skip-workflows` if the current releases are already present.
|
|
83
|
+
- **Terraform plan needs approval** → the helper approves it when run with `--yes`; without `--yes`, approve the prompt.
|
|
84
|
+
- **Auth/sign-in or tRPC routes fail after install** → verify the `__APP_NAME__` installation has `BETTER_AUTH_SECRET` and `ENCRYPTION_SECRET_KEY` secrets from `deploy/ryvn/percepta-test.secrets.env`, then redeploy `__APP_NAME__` so the pod reloads them.
|
|
91
85
|
- **Pod crash-looping** → check `ryvn logs`; migration or database connectivity failures are the most common fresh-deploy causes.
|
|
92
|
-
- **Database
|
|
93
|
-
- **
|
|
94
|
-
- **
|
|
95
|
-
- **
|
|
86
|
+
- **Database schema missing** → check `ryvn get installation __APP_NAME__-terraform -e percepta-test`.
|
|
87
|
+
- **Inngest can't reach the app** → `INNGEST_APP_URL` must use the k8s service name `__APP_NAME__-web-server`.
|
|
88
|
+
- **Platform preflight fails** → deploy or repair the missing shared installation first. This helper only wires apps into an existing environment.
|
|
89
|
+
- **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`.
|
|
90
|
+
- **LLM calls fail after deploy** → verify `demos-commons` has sensitive `ANTHROPIC_API_KEY` and the installation has `LLM_PROVIDER=anthropic`.
|
|
96
91
|
|
|
97
92
|
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.
|
|
@@ -31,13 +31,13 @@ Add the event to the central `AppEvents` registry:
|
|
|
31
31
|
import { DocumentProcessedPayload } from "./payloads/DocumentProcessedPayload";
|
|
32
32
|
|
|
33
33
|
export const AppEvents = {
|
|
34
|
-
"app/document.processed":
|
|
35
|
-
data: DocumentProcessedPayload.SCHEMA,
|
|
36
|
-
}),
|
|
34
|
+
"app/document.processed": DocumentProcessedPayload.SCHEMA,
|
|
37
35
|
};
|
|
38
36
|
```
|
|
39
37
|
|
|
40
|
-
Event names follow the convention `"app/<entity>.<action>"`.
|
|
38
|
+
Event names follow the convention `"app/<entity>.<action>"`. `AppEvents`
|
|
39
|
+
schemas validate `event.data`, so do not wrap payload schemas in another
|
|
40
|
+
`{ data: ... }` object.
|
|
41
41
|
|
|
42
42
|
## Adding a New Function
|
|
43
43
|
|
|
@@ -93,22 +93,27 @@ const functionCollections: InngestFunctionCollection[] = compact([
|
|
|
93
93
|
## Sending Events
|
|
94
94
|
|
|
95
95
|
```typescript
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
await AppWorkflowService.create().sendDocumentProcessed({
|
|
97
|
+
documentId: "abc",
|
|
98
|
+
userId: "user-1",
|
|
99
|
+
pageCount: 5,
|
|
100
100
|
});
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
Prefer adding typed methods to `AppWorkflowService` instead of calling
|
|
104
|
+
`InngestService.create().client.send(...)` directly from routers or route
|
|
105
|
+
handlers. Keep a unit test that asserts the method sends the same payload shape
|
|
106
|
+
that `AppEvents` validates.
|
|
107
|
+
|
|
103
108
|
## Running Inngest Locally
|
|
104
109
|
|
|
105
|
-
### 1.
|
|
110
|
+
### 1. Start the Inngest Dev Server
|
|
106
111
|
|
|
107
112
|
```bash
|
|
108
|
-
pnpm
|
|
113
|
+
pnpm inngest:dev
|
|
109
114
|
```
|
|
110
115
|
|
|
111
|
-
This starts the Inngest Dev Server
|
|
116
|
+
This starts the Inngest Dev Server for `http://localhost:3000/api/inngest` with a dashboard UI.
|
|
112
117
|
|
|
113
118
|
### 2. Set environment variables
|
|
114
119
|
|
|
@@ -126,7 +131,7 @@ INNGEST_EVENT_KEY=local # any value works locally
|
|
|
126
131
|
pnpm dev
|
|
127
132
|
```
|
|
128
133
|
|
|
129
|
-
The Inngest Dev Server auto-discovers functions by calling the serve endpoint at `/api/inngest`. Open `
|
|
134
|
+
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
135
|
|
|
131
136
|
## Environment Variables
|
|
132
137
|
|
|
@@ -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
|
|
@@ -180,7 +193,7 @@ git add -A
|
|
|
180
193
|
git commit -m "Initial implementation of <app-name>"
|
|
181
194
|
|
|
182
195
|
# Create the repo under the Percepta-Core org
|
|
183
|
-
gh repo create Percepta-Core/<app-name> --
|
|
196
|
+
gh repo create Percepta-Core/<app-name> --internal --source=. --push
|
|
184
197
|
```
|
|
185
198
|
|
|
186
199
|
If `gh` is not authenticated, tell the user to run `gh auth login` and then continue.
|
|
@@ -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
|
|