Use ceremony-observations.yml when the GPT must create Langfuse traces and append nested observations. It is intentionally smaller and more explicit than ceremony.yml so GPT Actions can reliably construct OTLP/HTTP JSON.
Langfuse v4 does not provide mutable REST trace and observation creation endpoints. A trace is the group of immutable observations sharing one traceId. The supported direct-ingestion path is:
POST /api/public/otel/v1/traces
Content-Type: application/json
Authorization: Basic <base64(public-key:secret-key)>
The observations_export action exposes that endpoint with a constrained OTLP schema and examples. It supports both:
parentSpanId.parentSpanId to an existing observation/span ID.The file has 23 actions, below the stated 30-action limit. Scores and the original media operations remain in this specification because the complete focused surface still fits. Destructive delete actions are omitted.
ceremony-observations.yml as the GPT Action schema.https://us.cloud.langfuse.comhttps://jp.cloud.langfuse.comhttps://hipaa.cloud.langfuse.comAuthorizationBasic BASE64_VALUEBASE64_VALUE is the base64 encoding of LANGFUSE_PUBLIC_KEY:LANGFUSE_SECRET_KEY without a trailing newline.Keep Code Interpreter/Data Analysis enabled if available. Before an export, use it to generate IDs and current timestamps rather than inventing them:
import secrets, time
trace_id = secrets.token_hex(16) # only for a new trace
span_id = secrets.token_hex(8) # fresh for every observation
start_ns = time.time_ns()
# perform/describe the observed work
end_ns = max(time.time_ns(), start_ns)
Custom GPT currently ignores ordinary OpenAPI header parameters, so this direct specification intentionally does not declare x-langfuse-ingestion-version. Langfuse documents that OTLP ingestion without that header still works, but data may take up to ten minutes to appear in v4 reads. If real-time visibility is mandatory, use a trusted proxy that adds x-langfuse-ingestion-version: 4 before forwarding the request to Langfuse.
Add the following behavior to the GPT’s instructions:
### Langfuse tracing
Use observations_export to record ceremony work.
When starting a new trace:
1. Generate a cryptographically random, non-zero, lowercase 32-hex traceId.
2. Generate a cryptographically random, non-zero, lowercase 16-hex root spanId.
3. Calculate current Unix epoch nanoseconds as a decimal string.
4. Export one complete root span without parentSpanId.
5. Preserve the returned/local traceId and root spanId for later steps.
When adding an observation:
1. Reuse the exact 32-hex traceId.
2. Generate a fresh non-zero 16-hex spanId. Never reuse a spanId.
3. Set parentSpanId to the exact 16-hex ID of its parent observation.
4. Export a complete observation with start and end epoch-nanosecond strings.
5. Repeat trace-level attributes on the child.
Every span must include langfuse.observation.type. Allowed values are span,
generation, or event. JSON input/output must be serialized into stringValue.
For generations, include langfuse.observation.model.name and, when known,
langfuse.observation.usage_details as a JSON string.
Treat exported spans as immutable. Do not retry with the same spanId to edit a
span. To record a correction or later result, append a new child event/span.
After exporting, call observations_list with the traceId and fields
core,basic,time,io,metadata,model,usage,trace_context to verify the hierarchy.
Use each returned observation id as the span/parent ID for later children.
### Langfuse media
When the user supplies media, use the actual file bytes—never placeholder text.
Compute contentLength from those bytes. Compute SHA-256 over those bytes and
send the standard padded Base64 digest, not hexadecimal, as sha256Hash.
Prefer media_uploadConversationFile from the companion proxy Action; pass the
user's actual conversation file in openaiFileIdRefs. It performs record
creation, the exact-byte PUT, and finalization and returns a mediaToken.
media_getUploadUrl alone only creates a record. Never claim success without a
completed PUT and media_patch, or success=true from the proxy bridge.
The parent trace and root observation must already exist. Replace every example value with live values; do not copy IDs or timestamps literally.
{
"resourceSpans": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": { "stringValue": "custom-gpt-ceremony" }
}
]
},
"scopeSpans": [
{
"scope": {
"name": "custom-gpt-ceremony",
"version": "0.4.2"
},
"spans": [
{
"traceId": "REPLACE_WITH_32_HEX_TRACE_ID",
"spanId": "REPLACE_16_HEX",
"parentSpanId": "REPLACE_16_HEX",
"name": "ceremony-step",
"kind": 1,
"startTimeUnixNano": "REPLACE_WITH_EPOCH_NANOSECONDS",
"endTimeUnixNano": "REPLACE_WITH_EPOCH_NANOSECONDS",
"attributes": [
{
"key": "langfuse.observation.type",
"value": { "stringValue": "span" }
},
{
"key": "langfuse.trace.name",
"value": { "stringValue": "ceremony" }
},
{
"key": "langfuse.observation.input",
"value": { "stringValue": "{\"step\":\"input\"}" }
},
{
"key": "langfuse.observation.output",
"value": { "stringValue": "{\"status\":\"complete\"}" }
}
],
"status": { "code": 1 }
}
]
}
]
}
]
}
| Group | Actions |
|---|---|
| Observations and trace ingestion | 2 |
| Projects | 1 |
| Prompts | 4 |
| Datasets and dataset items | 6 |
| Scores and score configurations | 5 |
| Comments | 2 |
| Media | 3 |
| Total | 23 |
When the user supplies media, always use that media’s actual bytes. Never create or hash placeholder text or surrogate bytes unless the user explicitly requests a placeholder.
contentType from those bytes/file and set contentLength to their exact byte length.sha256Hash; never send the 64-character hexadecimal digest.media_getUploadUrl with the context, file metadata, Base64 hash, and field.uploadUrl using the declared content type.media_patch with the completion time and HTTP result.media_get to verify the media record and obtain a temporary download URL.Example for the four UTF-8 bytes test:
sha256Hash: n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
Receiving uploadUrl creates a Langfuse media record but does not upload the bytes. Never report that media was uploaded after media_getUploadUrl alone. The direct OpenAPI document cannot describe a PUT to a dynamically returned storage host. For autonomous uploads, deploy the included coaiajs-media-proxy, import ceremony-media-proxy.yml as a second GPT Action, and use media_uploadConversationFile. The bridge consumes the user’s actual openaiFileIdRefs file, performs the complete upload flow, and returns the media token.
OpenAPI allows one operation for each HTTP method/path pair, and Langfuse uses the same OTLP endpoint for both behaviors. The observations_export action distinguishes them through span context:
traceId, new spanId, no parentSpanIdtraceId, new spanId, existing parent spanIdA friendlier pair of actions with bodies such as {traceId, parentId, input, output} would require a trusted proxy that transforms those requests into OTLP and adds the ingestion-version header. An OpenAPI document alone cannot change Langfuse’s wire contract.