From 0d19e9ac1aedb58a08c0f3f3d67546ed85211196 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Mon, 29 Jun 2026 12:28:44 +0200 Subject: [PATCH 1/2] feat(spans): send gen_ai spans using the span v2 protocol --- composer.json | 2 +- .../EnvelopItems/TransactionItem.php | 203 +++++++++++++++++- tests/Serializer/PayloadSerializerTest.php | 73 +++++++ 3 files changed, 276 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index d231477c6..8605380ff 100644 --- a/composer.json +++ b/composer.json @@ -76,7 +76,7 @@ "tests": "vendor/bin/phpunit --verbose", "cs-check": "vendor/bin/php-cs-fixer fix --verbose --diff --dry-run", "cs-fix": "vendor/bin/php-cs-fixer fix --verbose --diff", - "phpstan": "vendor/bin/phpstan analyse", + "phpstan": "vendor/bin/phpstan analyse --memory-limit=512M", "mago": "vendor/bin/mago --config=mago.toml analyze" }, "config": { diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php index 28baf99ea..84adf66e9 100644 --- a/src/Serializer/EnvelopItems/TransactionItem.php +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -8,6 +8,7 @@ use Sentry\EventType; use Sentry\Serializer\Traits\BreadcrumbSeralizerTrait; use Sentry\Tracing\Span; +use Sentry\Tracing\SpanStatus; use Sentry\Tracing\TransactionMetadata; use Sentry\Util\JSON; @@ -21,6 +22,14 @@ * count: int, * tags: array, * } + * + * @phpstan-import-type AttributeValue from \Sentry\Attributes\Attribute + * + * @phpstan-type SerializedSpanV2AttributeArrayValue array + * @phpstan-type SerializedSpanV2AttributeType 'string'|'boolean'|'integer'|'double'|'array' + * @phpstan-type SerializedSpanV2AttributeValue AttributeValue|SerializedSpanV2AttributeArrayValue + * @phpstan-type SerializedSpanV2Attribute array{type: SerializedSpanV2AttributeType, value: SerializedSpanV2AttributeValue} + * @phpstan-type SerializedSpanV2Attributes array */ class TransactionItem implements EnvelopeItemInterface { @@ -28,6 +37,17 @@ class TransactionItem implements EnvelopeItemInterface public static function toEnvelopeItem(Event $event): string { + $transactionSpans = []; + $genAiSpans = []; + + foreach ($event->getSpans() as $span) { + if (strpos($span->getOp() ?? '', 'gen_ai.') === 0) { + $genAiSpans[] = $span; + } else { + $transactionSpans[] = $span; + } + } + $header = [ 'type' => (string) EventType::transaction(), 'content_type' => 'application/json', @@ -121,13 +141,29 @@ public static function toEnvelopeItem(Event $event): string $payload['request'] = $event->getRequest(); } - $payload['spans'] = array_values(array_map([self::class, 'serializeSpan'], $event->getSpans())); + $payload['spans'] = array_values(array_map([self::class, 'serializeSpan'], $transactionSpans)); $transactionMetadata = $event->getSdkMetadata('transaction_metadata'); if ($transactionMetadata instanceof TransactionMetadata) { $payload['transaction_info']['source'] = (string) $transactionMetadata->getSource(); } + if (\count($genAiSpans) > 0) { + $genAi = []; + $genAi['items'] = array_map(static function (Span $span) use ($event) { + return self::serializeSpanV2($span, $event); + }, $genAiSpans); + $genAi['version'] = 2; + + $genAiHeaders = [ + 'type' => 'span', + 'item_count' => \count($genAiSpans), + 'content_type' => 'application/vnd.sentry.items.span.v2+json', + ]; + + return \sprintf("%s\n%s\n%s\n%s", JSON::encode($header), JSON::encode($payload), JSON::encode($genAiHeaders), JSON::encode($genAi)); + } + return \sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); } @@ -187,4 +223,169 @@ protected static function serializeSpan(Span $span): array return $result; } + + /** + * @return array + * + * @phpstan-return array{ + * trace_id: string, + * span_id: string, + * name: string|null, + * is_segment: false, + * start_timestamp: float, + * attributes: SerializedSpanV2Attributes, + * status: 'ok'|'error', + * end_timestamp?: float|null, + * parent_span_id?: string, + * } + */ + protected static function serializeSpanV2(Span $span, Event $event): array + { + $result = [ + 'trace_id' => (string) $span->getTraceId(), + 'span_id' => (string) $span->getSpanId(), + 'name' => $span->getDescription() ?? $span->getOp(), + 'is_segment' => false, + 'start_timestamp' => $span->getStartTimestamp(), + 'attributes' => self::collectV2Attributes($span, $event), + 'status' => 'ok', + ]; + if ($span->getEndTimestamp() !== null) { + $result['end_timestamp'] = $span->getEndTimestamp(); + } + if ($span->getStatus() !== null) { + $result['status'] = $span->getStatus() === SpanStatus::ok() ? 'ok' : 'error'; + } + if ($span->getParentSpanId() !== null) { + $result['parent_span_id'] = (string) $span->getParentSpanId(); + } + + return $result; + } + + /** + * @return array + * + * @phpstan-return SerializedSpanV2Attributes + * + * @mago-ignore analysis:redundant-null-coalesce + * @mago-ignore analysis:mixed-assignment + */ + private static function collectV2Attributes(Span $span, Event $event): array + { + /** @var SerializedSpanV2Attributes $attributes */ + $attributes = []; + + self::setStringAttribute($attributes, 'sentry.op', $span->getOp()); + self::setStringAttribute($attributes, 'sentry.origin', $span->getOrigin() ?? 'manual'); + self::setStringAttribute($attributes, 'sentry.release', $event->getRelease()); + self::setStringAttribute($attributes, 'sentry.environment', $event->getEnvironment()); + self::setStringAttribute($attributes, 'server.address', $event->getServerName()); + self::setStringAttribute($attributes, 'sentry.segment.name', $event->getTransaction()); + self::setStringAttribute($attributes, 'sentry.sdk.name', $event->getSdkPayload()['name'] ?? null); + self::setStringAttribute($attributes, 'sentry.sdk.version', $event->getSdkPayload()['version'] ?? null); + + $runtimeContext = $event->getRuntimeContext(); + if ($runtimeContext !== null) { + self::setStringAttribute($attributes, 'process.runtime.name', $runtimeContext->getName()); + self::setStringAttribute($attributes, 'process.runtime.version', $runtimeContext->getVersion()); + } + + $user = $event->getUser(); + if ($user !== null) { + self::setAttribute($attributes, 'user.id', $user->getId()); + self::setAttribute($attributes, 'user.name', $user->getUsername()); + self::setAttribute($attributes, 'user.email', $user->getEmail()); + } + + self::setAttribute($attributes, 'sentry.segment.id', $event->getContexts()['trace']['span_id'] ?? null); + + foreach ($span->getTags() as $key => $value) { + self::setStringAttribute($attributes, $key, $value); + } + + foreach ($span->getData() as $key => $value) { + self::setAttribute($attributes, $key, $value); + } + + unset($attributes['status']); + + return $attributes; + } + + /** + * @param mixed $value + * + * @phpstan-param SerializedSpanV2Attributes $attributes + */ + private static function setAttribute(&$attributes, string $key, $value): void + { + if (\is_array($value)) { + if ($value === [] || self::isHomogeneousScalarArray($value)) { + self::setTypeAttribute($attributes, $key, 'array', $value); + } + + return; + } + + $attribute = \Sentry\Attributes\Attribute::tryFromValue($value); + if ($attribute === null) { + return; + } + + self::setTypeAttribute($attributes, $key, $attribute->getType(), $attribute->getValue()); + } + + /** + * @phpstan-param SerializedSpanV2Attributes $attributes + * @phpstan-param SerializedSpanV2AttributeType $type + * @phpstan-param SerializedSpanV2AttributeValue|null $value + */ + private static function setTypeAttribute(&$attributes, string $key, string $type, $value): void + { + if ($value === null) { + return; + } + $attributes[$key] = [ + 'type' => $type, + 'value' => $value, + ]; + } + + /** + * @phpstan-param SerializedSpanV2Attributes $attributes + */ + private static function setStringAttribute(&$attributes, string $key, ?string $value): void + { + self::setTypeAttribute($attributes, $key, 'string', $value); + } + + /** + * @param array $arr + * + * @phpstan-assert-if-true SerializedSpanV2AttributeArrayValue $arr + * + * @mago-ignore analysis:mixed-assignment + */ + private static function isHomogeneousScalarArray(array $arr): bool + { + $type = null; + $index = 0; + + foreach ($arr as $key => $value) { + if ($key !== $index++ || !\is_scalar($value)) { + return false; + } + + $itemType = \gettype($value); + + if ($type === null) { + $type = $itemType; + } elseif ($type !== $itemType) { + return false; + } + } + + return true; + } } diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 41e3968ec..212920431 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -311,6 +311,79 @@ public static function serializeAsEnvelopeDataProvider(): iterable , ]; + $regularSpan = new Span(); + $regularSpan->setSpanId(new SpanId('b01b9f6349558cd1')); + $regularSpan->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); + $regularSpan->setParentSpanId(new SpanId('5dd538dc297544cc')); + $regularSpan->setOp('http.client'); + $regularSpan->setDescription('GET https://api.example.com/models'); + $regularSpan->setStatus(SpanStatus::ok()); + $regularSpan->setStartTimestamp(1597790836); + $regularSpan->setData([ + 'url' => 'https://api.example.com/models', + 'method' => 'GET', + ]); + $regularSpan->setTags(['http.status_code' => '200']); + $regularSpan->finish(1597790836.25); + + $genAiSpan1 = new Span(); + $genAiSpan1->setSpanId(new SpanId('a01b9f6349558cd1')); + $genAiSpan1->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); + $genAiSpan1->setParentSpanId(new SpanId('b01b9f6349558cd1')); + $genAiSpan1->setOp('gen_ai.chat'); + $genAiSpan1->setDescription('chat.completions create'); + $genAiSpan1->setStatus(SpanStatus::ok()); + $genAiSpan1->setStartTimestamp(1597790836.5); + $genAiSpan1->setOrigin('auto.ai.openai'); + $genAiSpan1->setTags([ + 'ai.provider' => 'openai', + 'ai.operation' => 'chat', + ]); + $genAiSpan1->setData([ + 'gen_ai.request.model' => 'gpt-4o-mini', + 'gen_ai.response.streaming' => true, + 'gen_ai.usage.input_tokens' => 12, + 'gen_ai.request.temperature' => 0.7, + 'gen_ai.response.finish_reasons' => ['stop', 'length'], + 'gen_ai.unsupported.mixed' => ['stop', 1], + 'gen_ai.unsupported.nested' => [['reason' => 'stop']], + ]); + $genAiSpan1->finish(1597790837.25); + + $genAiSpan2 = new Span(); + $genAiSpan2->setSpanId(new SpanId('a01b9f6349558cd2')); + $genAiSpan2->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); + $genAiSpan2->setParentSpanId(new SpanId('a01b9f6349558cd1')); + $genAiSpan2->setOp('gen_ai.embeddings'); + $genAiSpan2->setDescription('embeddings create'); + $genAiSpan2->setStatus(SpanStatus::internalError()); + $genAiSpan2->setStartTimestamp(1597790837.5); + $genAiSpan2->setData([ + 'gen_ai.request.model' => 'text-embedding-3-small', + 'gen_ai.usage.input_tokens' => 7, + ]); + $genAiSpan2->finish(1597790838); + + $event = Event::createTransaction(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setSpans([$regularSpan, $genAiSpan1, $genAiSpan2]); + $event->setTransaction('POST /ai/chat'); + $event->setContext('trace', [ + 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', + 'span_id' => '5dd538dc297544cc', + ]); + + yield [ + $event, + <<setSdkMetadata('dynamic_sampling_context', DynamicSamplingContext::fromHeader('sentry-public_key=public,sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-sample_rate=1')); $event->setSdkMetadata('transaction_metadata', new TransactionMetadata()); From cbfa297ac7757646c0f7fae657693105703db8ff Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Mon, 29 Jun 2026 15:02:53 +0200 Subject: [PATCH 2/2] use attribute bag --- src/Attributes/AttributeBag.php | 10 ++ .../EnvelopItems/TransactionItem.php | 145 +++--------------- tests/Serializer/PayloadSerializerTest.php | 5 +- 3 files changed, 35 insertions(+), 125 deletions(-) diff --git a/src/Attributes/AttributeBag.php b/src/Attributes/AttributeBag.php index 0d5b3018b..b9f7226f8 100644 --- a/src/Attributes/AttributeBag.php +++ b/src/Attributes/AttributeBag.php @@ -30,6 +30,16 @@ public function set(string $key, $value): self return $this; } + /** + * @param mixed $value + */ + public function setUnlessNull(string $key, $value): void + { + if ($value !== null) { + self::set($key, $value); + } + } + public function get(string $key): ?Attribute { return $this->attributes[$key] ?? null; diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php index 84adf66e9..f0b81d294 100644 --- a/src/Serializer/EnvelopItems/TransactionItem.php +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -4,6 +4,8 @@ namespace Sentry\Serializer\EnvelopItems; +use Sentry\Attributes\Attribute; +use Sentry\Attributes\AttributeBag; use Sentry\Event; use Sentry\EventType; use Sentry\Serializer\Traits\BreadcrumbSeralizerTrait; @@ -22,14 +24,6 @@ * count: int, * tags: array, * } - * - * @phpstan-import-type AttributeValue from \Sentry\Attributes\Attribute - * - * @phpstan-type SerializedSpanV2AttributeArrayValue array - * @phpstan-type SerializedSpanV2AttributeType 'string'|'boolean'|'integer'|'double'|'array' - * @phpstan-type SerializedSpanV2AttributeValue AttributeValue|SerializedSpanV2AttributeArrayValue - * @phpstan-type SerializedSpanV2Attribute array{type: SerializedSpanV2AttributeType, value: SerializedSpanV2AttributeValue} - * @phpstan-type SerializedSpanV2Attributes array */ class TransactionItem implements EnvelopeItemInterface { @@ -233,7 +227,7 @@ protected static function serializeSpan(Span $span): array * name: string|null, * is_segment: false, * start_timestamp: float, - * attributes: SerializedSpanV2Attributes, + * attributes: array, * status: 'ok'|'error', * end_timestamp?: float|null, * parent_span_id?: string, @@ -247,7 +241,12 @@ protected static function serializeSpanV2(Span $span, Event $event): array 'name' => $span->getDescription() ?? $span->getOp(), 'is_segment' => false, 'start_timestamp' => $span->getStartTimestamp(), - 'attributes' => self::collectV2Attributes($span, $event), + 'attributes' => array_map(static function (Attribute $value) { + return [ + 'type' => $value->getType(), + 'value' => $value->getValue(), + ]; + }, self::collectV2Attributes($span, $event)->all()), 'status' => 'ok', ]; if ($span->getEndTimestamp() !== null) { @@ -263,129 +262,33 @@ protected static function serializeSpanV2(Span $span, Event $event): array return $result; } - /** - * @return array - * - * @phpstan-return SerializedSpanV2Attributes - * + /*** * @mago-ignore analysis:redundant-null-coalesce * @mago-ignore analysis:mixed-assignment */ - private static function collectV2Attributes(Span $span, Event $event): array + private static function collectV2Attributes(Span $span, Event $event): AttributeBag { - /** @var SerializedSpanV2Attributes $attributes */ - $attributes = []; - - self::setStringAttribute($attributes, 'sentry.op', $span->getOp()); - self::setStringAttribute($attributes, 'sentry.origin', $span->getOrigin() ?? 'manual'); - self::setStringAttribute($attributes, 'sentry.release', $event->getRelease()); - self::setStringAttribute($attributes, 'sentry.environment', $event->getEnvironment()); - self::setStringAttribute($attributes, 'server.address', $event->getServerName()); - self::setStringAttribute($attributes, 'sentry.segment.name', $event->getTransaction()); - self::setStringAttribute($attributes, 'sentry.sdk.name', $event->getSdkPayload()['name'] ?? null); - self::setStringAttribute($attributes, 'sentry.sdk.version', $event->getSdkPayload()['version'] ?? null); - - $runtimeContext = $event->getRuntimeContext(); - if ($runtimeContext !== null) { - self::setStringAttribute($attributes, 'process.runtime.name', $runtimeContext->getName()); - self::setStringAttribute($attributes, 'process.runtime.version', $runtimeContext->getVersion()); - } - - $user = $event->getUser(); - if ($user !== null) { - self::setAttribute($attributes, 'user.id', $user->getId()); - self::setAttribute($attributes, 'user.name', $user->getUsername()); - self::setAttribute($attributes, 'user.email', $user->getEmail()); - } - - self::setAttribute($attributes, 'sentry.segment.id', $event->getContexts()['trace']['span_id'] ?? null); + $attributes = new AttributeBag(); + $attributes->setUnlessNull('sentry.op', $span->getOp()); + $attributes->set('sentry.origin', $span->getOrigin() ?? 'manual'); + $attributes->setUnlessNull('sentry.release', $event->getRelease()); + $attributes->setUnlessNull('sentry.environment', $event->getEnvironment()); + $attributes->setUnlessNull('server.address', $event->getServerName()); + $attributes->setUnlessNull('sentry.segment.name', $event->getTransaction()); + $attributes->set('sentry.sdk.name', $event->getSdkPayload()['name'] ?? null); + $attributes->set('sentry.sdk.version', $event->getSdkPayload()['version'] ?? null); + $attributes->set('sentry.segment.id', $event->getContexts()['trace']['span_id'] ?? null); foreach ($span->getTags() as $key => $value) { - self::setStringAttribute($attributes, $key, $value); + $attributes->set($key, $value); } foreach ($span->getData() as $key => $value) { - self::setAttribute($attributes, $key, $value); + $attributes->set($key, $value); } - unset($attributes['status']); + $attributes->forget('status'); return $attributes; } - - /** - * @param mixed $value - * - * @phpstan-param SerializedSpanV2Attributes $attributes - */ - private static function setAttribute(&$attributes, string $key, $value): void - { - if (\is_array($value)) { - if ($value === [] || self::isHomogeneousScalarArray($value)) { - self::setTypeAttribute($attributes, $key, 'array', $value); - } - - return; - } - - $attribute = \Sentry\Attributes\Attribute::tryFromValue($value); - if ($attribute === null) { - return; - } - - self::setTypeAttribute($attributes, $key, $attribute->getType(), $attribute->getValue()); - } - - /** - * @phpstan-param SerializedSpanV2Attributes $attributes - * @phpstan-param SerializedSpanV2AttributeType $type - * @phpstan-param SerializedSpanV2AttributeValue|null $value - */ - private static function setTypeAttribute(&$attributes, string $key, string $type, $value): void - { - if ($value === null) { - return; - } - $attributes[$key] = [ - 'type' => $type, - 'value' => $value, - ]; - } - - /** - * @phpstan-param SerializedSpanV2Attributes $attributes - */ - private static function setStringAttribute(&$attributes, string $key, ?string $value): void - { - self::setTypeAttribute($attributes, $key, 'string', $value); - } - - /** - * @param array $arr - * - * @phpstan-assert-if-true SerializedSpanV2AttributeArrayValue $arr - * - * @mago-ignore analysis:mixed-assignment - */ - private static function isHomogeneousScalarArray(array $arr): bool - { - $type = null; - $index = 0; - - foreach ($arr as $key => $value) { - if ($key !== $index++ || !\is_scalar($value)) { - return false; - } - - $itemType = \gettype($value); - - if ($type === null) { - $type = $itemType; - } elseif ($type !== $itemType) { - return false; - } - } - - return true; - } } diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 212920431..6ce4b6749 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -344,9 +344,6 @@ public static function serializeAsEnvelopeDataProvider(): iterable 'gen_ai.response.streaming' => true, 'gen_ai.usage.input_tokens' => 12, 'gen_ai.request.temperature' => 0.7, - 'gen_ai.response.finish_reasons' => ['stop', 'length'], - 'gen_ai.unsupported.mixed' => ['stop', 1], - 'gen_ai.unsupported.nested' => [['reason' => 'stop']], ]); $genAiSpan1->finish(1597790837.25); @@ -379,7 +376,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable {"type":"transaction","content_type":"application\/json"} {"timestamp":1597790835,"platform":"php","sdk":{"name":"sentry.php","version":"$sdkVersion","packages":[{"name":"composer:sentry\/sentry","version":"$sdkVersion"}]},"transaction":"POST \/ai\/chat","contexts":{"trace":{"trace_id":"21160e9b836d479f81611368b2aa3d2c","span_id":"5dd538dc297544cc"}},"spans":[{"span_id":"b01b9f6349558cd1","trace_id":"21160e9b836d479f81611368b2aa3d2c","start_timestamp":1597790836,"origin":"manual","parent_span_id":"5dd538dc297544cc","timestamp":1597790836.25,"status":"ok","description":"GET https:\/\/api.example.com\/models","op":"http.client","data":{"url":"https:\/\/api.example.com\/models","method":"GET"},"tags":{"http.status_code":"200"}}]} {"type":"span","item_count":2,"content_type":"application\/vnd.sentry.items.span.v2+json"} -{"items":[{"trace_id":"21160e9b836d479f81611368b2aa3d2c","span_id":"a01b9f6349558cd1","name":"chat.completions create","is_segment":false,"start_timestamp":1597790836.5,"attributes":{"sentry.op":{"type":"string","value":"gen_ai.chat"},"sentry.origin":{"type":"string","value":"auto.ai.openai"},"sentry.segment.name":{"type":"string","value":"POST \/ai\/chat"},"sentry.sdk.name":{"type":"string","value":"sentry.php"},"sentry.sdk.version":{"type":"string","value":"$sdkVersion"},"sentry.segment.id":{"type":"string","value":"5dd538dc297544cc"},"ai.provider":{"type":"string","value":"openai"},"ai.operation":{"type":"string","value":"chat"},"gen_ai.request.model":{"type":"string","value":"gpt-4o-mini"},"gen_ai.response.streaming":{"type":"boolean","value":true},"gen_ai.usage.input_tokens":{"type":"integer","value":12},"gen_ai.request.temperature":{"type":"double","value":0.7},"gen_ai.response.finish_reasons":{"type":"array","value":["stop","length"]}},"status":"ok","end_timestamp":1597790837.25,"parent_span_id":"b01b9f6349558cd1"},{"trace_id":"21160e9b836d479f81611368b2aa3d2c","span_id":"a01b9f6349558cd2","name":"embeddings create","is_segment":false,"start_timestamp":1597790837.5,"attributes":{"sentry.op":{"type":"string","value":"gen_ai.embeddings"},"sentry.origin":{"type":"string","value":"manual"},"sentry.segment.name":{"type":"string","value":"POST \/ai\/chat"},"sentry.sdk.name":{"type":"string","value":"sentry.php"},"sentry.sdk.version":{"type":"string","value":"$sdkVersion"},"sentry.segment.id":{"type":"string","value":"5dd538dc297544cc"},"gen_ai.request.model":{"type":"string","value":"text-embedding-3-small"},"gen_ai.usage.input_tokens":{"type":"integer","value":7}},"status":"error","end_timestamp":1597790838,"parent_span_id":"a01b9f6349558cd1"}],"version":2} +{"items":[{"trace_id":"21160e9b836d479f81611368b2aa3d2c","span_id":"a01b9f6349558cd1","name":"chat.completions create","is_segment":false,"start_timestamp":1597790836.5,"attributes":{"sentry.op":{"type":"string","value":"gen_ai.chat"},"sentry.origin":{"type":"string","value":"auto.ai.openai"},"sentry.segment.name":{"type":"string","value":"POST \/ai\/chat"},"sentry.sdk.name":{"type":"string","value":"sentry.php"},"sentry.sdk.version":{"type":"string","value":"$sdkVersion"},"sentry.segment.id":{"type":"string","value":"5dd538dc297544cc"},"ai.provider":{"type":"string","value":"openai"},"ai.operation":{"type":"string","value":"chat"},"gen_ai.request.model":{"type":"string","value":"gpt-4o-mini"},"gen_ai.response.streaming":{"type":"boolean","value":true},"gen_ai.usage.input_tokens":{"type":"integer","value":12},"gen_ai.request.temperature":{"type":"double","value":0.7}},"status":"ok","end_timestamp":1597790837.25,"parent_span_id":"b01b9f6349558cd1"},{"trace_id":"21160e9b836d479f81611368b2aa3d2c","span_id":"a01b9f6349558cd2","name":"embeddings create","is_segment":false,"start_timestamp":1597790837.5,"attributes":{"sentry.op":{"type":"string","value":"gen_ai.embeddings"},"sentry.origin":{"type":"string","value":"manual"},"sentry.segment.name":{"type":"string","value":"POST \/ai\/chat"},"sentry.sdk.name":{"type":"string","value":"sentry.php"},"sentry.sdk.version":{"type":"string","value":"$sdkVersion"},"sentry.segment.id":{"type":"string","value":"5dd538dc297544cc"},"gen_ai.request.model":{"type":"string","value":"text-embedding-3-small"},"gen_ai.usage.input_tokens":{"type":"integer","value":7}},"status":"error","end_timestamp":1597790838,"parent_span_id":"a01b9f6349558cd1"}],"version":2} TEXT , ];