Skip to content

Commit c037cb2

Browse files
committed
feat: Otel Observabillity
1 parent 5e2a424 commit c037cb2

File tree

8 files changed

+968
-19
lines changed

8 files changed

+968
-19
lines changed

composer.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
"guzzlehttp/psr7": "^2.8",
2424
"illuminate/collections": "^12.49",
2525
"laravel/prompts": "^0.3.8",
26+
"open-telemetry/api": "^1.8",
27+
"open-telemetry/exporter-otlp": "^1.4",
28+
"open-telemetry/sdk": "^1.13",
2629
"openai-php/client": "^0.18",
2730
"php-mcp/client": "^1.0",
2831
"psr-discovery/cache-implementations": "^1.2",
@@ -104,7 +107,8 @@
104107
"allow-plugins": {
105108
"dealerdirect/phpcodesniffer-composer-installer": true,
106109
"pestphp/pest-plugin": true,
107-
"php-http/discovery": true
110+
"php-http/discovery": true,
111+
"tbachert/spi": true
108112
}
109113
},
110114
"extra": {

config/cortex.php

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
declare(strict_types=1);
44

5+
use Cortex\Agents\Prebuilt\WeatherAgent;
56
use Cortex\LLM\Enums\LLMDriver;
67
use Cortex\LLM\Enums\StreamingProtocol;
7-
use Cortex\Agents\Prebuilt\WeatherAgent;
8-
use Cortex\ModelInfo\Providers\OllamaModelInfoProvider;
98
use Cortex\ModelInfo\Providers\LiteLLMModelInfoProvider;
109
use Cortex\ModelInfo\Providers\LMStudioModelInfoProvider;
10+
use Cortex\ModelInfo\Providers\OllamaModelInfoProvider;
11+
use OpenTelemetry\Contrib\Otlp\Protocols;
1112

1213
return [
1314
/*
@@ -367,4 +368,45 @@
367368
|
368369
*/
369370
'default_streaming_protocol' => StreamingProtocol::Vercel,
371+
372+
/*
373+
|--------------------------------------------------------------------------
374+
| OpenTelemetry Tracing
375+
|--------------------------------------------------------------------------
376+
|
377+
| Configure OpenTelemetry tracing to export spans for agent runs, LLM calls,
378+
| tool executions, and agent steps to any OTLP-compatible backend
379+
| (e.g. Jaeger, Grafana Tempo, Honeycomb, Datadog).
380+
|
381+
| Set CORTEX_TRACING_ENABLED=true and point OTEL_EXPORTER_OTLP_ENDPOINT
382+
| at your collector or backend to get started.
383+
|
384+
*/
385+
'tracing' => [
386+
'enabled' => env('CORTEX_TRACING_ENABLED', true),
387+
388+
'exporter' => [
389+
/**
390+
* The OTLP endpoint to export spans to.
391+
* For HTTP/protobuf (default): http://localhost:4318/v1/traces
392+
* For gRPC: http://localhost:4317
393+
*/
394+
'endpoint' => env('OTEL_EXPORTER_OTLP_ENDPOINT', 'http://localhost:4318/v1/traces'),
395+
396+
/**
397+
* Supported: "http/protobuf", "http/json"
398+
*/
399+
'protocol' => env('OTEL_EXPORTER_OTLP_PROTOCOL', Protocols::HTTP_PROTOBUF),
400+
401+
/**
402+
* The headers to send with the request. Comma separated list of key=value pairs.
403+
*/
404+
'headers' => env('OTEL_EXPORTER_OTLP_HEADERS', ''),
405+
],
406+
407+
/**
408+
* The service name reported to the tracing backend.
409+
*/
410+
'service_name' => env('OTEL_SERVICE_NAME', 'cortex'),
411+
],
370412
];

src/CortexServiceProvider.php

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,32 @@
55
namespace Cortex;
66

77
use Throwable;
8-
use Monolog\Logger;
98
use Cortex\LLM\LLMManager;
109
use Cortex\Agents\Registry;
1110
use Cortex\Console\AgentChat;
1211
use Cortex\LLM\Contracts\LLM;
1312
use Cortex\Mcp\McpServerManager;
14-
use Monolog\Handler\StreamHandler;
15-
use Monolog\Formatter\LineFormatter;
1613
use Illuminate\Support\Facades\Blade;
1714
use Cortex\ModelInfo\ModelInfoFactory;
1815
use Spatie\LaravelPackageTools\Package;
1916
use Cortex\Embeddings\EmbeddingsManager;
2017
use Cortex\Prompts\PromptFactoryManager;
18+
use OpenTelemetry\Contrib\Otlp\Protocols;
2119
use Cortex\Embeddings\Contracts\Embeddings;
2220
use Cortex\Prompts\Contracts\PromptFactory;
21+
use OpenTelemetry\Contrib\Otlp\SpanExporter;
22+
use OpenTelemetry\SDK\Resource\ResourceInfo;
2323
use Illuminate\Contracts\Container\Container;
2424
use Cortex\Support\Events\InternalEventDispatcher;
25+
use OpenTelemetry\SDK\Common\Attribute\Attributes;
26+
use OpenTelemetry\SDK\Common\Util\ShutdownHandler;
27+
use OpenTelemetry\SDK\Trace\TracerProviderBuilder;
28+
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
2529
use Spatie\LaravelPackageTools\PackageServiceProvider;
26-
use Cortex\Support\Events\Subscribers\LoggingSubscriber;
30+
use OpenTelemetry\SemConv\Attributes\ServiceAttributes;
31+
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
32+
use Cortex\Support\Events\Subscribers\OpenTelemetrySubscriber;
33+
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
2734

2835
class CortexServiceProvider extends PackageServiceProvider
2936
{
@@ -55,6 +62,7 @@ public function packageBooted(): void
5562
$this->registerBladeDirectives();
5663

5764
$this->setupLogging();
65+
$this->setupTracing();
5866
}
5967

6068
protected function registerBladeDirectives(): void
@@ -163,12 +171,64 @@ protected function setupLogging(): void
163171
}
164172

165173
// TODO: This will be configurable.
166-
$logger = new Logger('cortex');
167-
$handler = new StreamHandler('php://stdout');
168-
$handler->setFormatter(new LineFormatter());
174+
// $logger = new Logger('cortex');
175+
// $handler = new StreamHandler('php://stdout');
176+
// $handler->setFormatter(new LineFormatter());
169177

170-
$logger->pushHandler($handler);
178+
// $logger->pushHandler($handler);
171179

172-
InternalEventDispatcher::instance()->subscribe(new LoggingSubscriber($logger));
180+
// InternalEventDispatcher::instance()->subscribe(new LoggingSubscriber($logger));
181+
}
182+
183+
protected function setupTracing(): void
184+
{
185+
if ($this->app->runningUnitTests()) {
186+
return;
187+
}
188+
189+
if (! config('cortex.tracing.enabled', false)) {
190+
return;
191+
}
192+
193+
$endpoint = (string) config('cortex.tracing.exporter.endpoint', 'http://localhost:4318/v1/traces');
194+
$protocol = (string) config('cortex.tracing.exporter.protocol', Protocols::HTTP_PROTOBUF);
195+
$serviceName = (string) config('cortex.tracing.service_name', 'cortex');
196+
$rawHeaders = config('cortex.tracing.exporter.headers', '');
197+
$headers = [];
198+
199+
foreach (explode(',', $rawHeaders) as $header) {
200+
$header = trim($header);
201+
if ($header === '') {
202+
continue;
203+
}
204+
$parts = explode('=', $header, 2);
205+
if (count($parts) === 2) {
206+
[$key, $value] = $parts;
207+
$headers[trim($key)] = trim($value, " \t\n\r\0\x0B\"'");
208+
}
209+
}
210+
211+
$resource = ResourceInfoFactory::emptyResource()->merge(
212+
ResourceInfo::create(Attributes::create([
213+
ServiceAttributes::SERVICE_NAME => $serviceName,
214+
])),
215+
);
216+
217+
$transport = new OtlpHttpTransportFactory()->create(
218+
$endpoint,
219+
Protocols::contentType($protocol),
220+
$headers,
221+
);
222+
223+
$exporter = new SpanExporter($transport);
224+
225+
$tracerProvider = new TracerProviderBuilder()
226+
->addSpanProcessor(new SimpleSpanProcessor($exporter))
227+
->setResource($resource)
228+
->build();
229+
230+
ShutdownHandler::register($tracerProvider->shutdown(...));
231+
232+
InternalEventDispatcher::instance()->subscribe(new OpenTelemetrySubscriber($tracerProvider));
173233
}
174234
}

src/LLM/AbstractLLM.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,11 @@ public function getMaxTokens(): ?int
412412
return $this->maxTokens;
413413
}
414414

415+
public function getToolConfig(): ?ToolConfig
416+
{
417+
return $this->toolConfig;
418+
}
419+
415420
public function isStreaming(): bool
416421
{
417422
return $this->streaming;

src/LLM/Contracts/LLM.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,17 @@
66

77
use Closure;
88
use Cortex\Contracts\Pipeable;
9+
use Cortex\JsonSchema\Types\ObjectSchema;
910
use Cortex\LLM\Data\ChatResult;
11+
use Cortex\LLM\Data\ChatStreamResult;
12+
use Cortex\LLM\Data\Messages\MessageCollection;
13+
use Cortex\LLM\Data\StructuredOutputConfig;
14+
use Cortex\LLM\Data\ToolConfig;
15+
use Cortex\LLM\Enums\StructuredOutputMode;
1016
use Cortex\LLM\Enums\ToolChoice;
1117
use Cortex\ModelInfo\Data\ModelInfo;
12-
use Cortex\LLM\Data\ChatStreamResult;
1318
use Cortex\ModelInfo\Enums\ModelFeature;
14-
use Cortex\JsonSchema\Types\ObjectSchema;
1519
use Cortex\ModelInfo\Enums\ModelProvider;
16-
use Cortex\LLM\Enums\StructuredOutputMode;
17-
use Cortex\LLM\Data\StructuredOutputConfig;
18-
use Cortex\LLM\Data\Messages\MessageCollection;
1920

2021
interface LLM extends Pipeable
2122
{
@@ -192,6 +193,11 @@ public function getStructuredOutputConfig(): ?StructuredOutputConfig;
192193
*/
193194
public function getStructuredOutputMode(): StructuredOutputMode;
194195

196+
/**
197+
* Get the tool config for the LLM.
198+
*/
199+
public function getToolConfig(): ?ToolConfig;
200+
195201
/**
196202
* Set whether the raw provider response should be included in the result, if available.
197203
*/

0 commit comments

Comments
 (0)