baikai-trace-otel 0.4.0.2 → 0.4.1.0
raw patch · 4 files changed
+132/−24 lines, 4 filesdep ~baikaiPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: baikai
API changes (from Hackage documentation)
Files
- CHANGELOG.md +85/−0
- baikai-trace-otel.cabal +3/−3
- src/Baikai/Trace/Sink/OpenTelemetry.hs +21/−19
- test/Main.hs +23/−2
CHANGELOG.md view
@@ -7,6 +7,91 @@ ## [Unreleased] +## [baikai 0.9.0.0] - 2026-10-25++### Added++- `Baikai.Judgment` compiles closed-set schemas into ordered questions, refuses+ generation controls and validates neutral answers without normalizing provider+ probabilities or deriving confidence and expected scores.+- `UserData`, `AssistantData`/`DataContent`, user-data message helpers, atomic+ data events, and named full-response JSON codecs preserve structured answers.+- `TypeSafeSystemOne`, catalog-owned `OutputKind`/`isJudgmentOnly`, `InputData`,+ and `typesafe_jev_latest`/`typesafe_jev_preview`. Legacy model JSON defaults to+ generative output. Register the new `baikai-typesafe` provider explicitly.++### Changed (breaking)++- Public content/event sums and model, error, trace and log records gain fields+ or constructors. Update exhaustive matches and record construction on upgrade.+- Terminal traces and both call-log paths preserve structured answer payloads.+ Evidence schema 2.6 includes structured response commitments and typed request+ problems; existing digest rules are unchanged.++## [baikai-typesafe 0.1.0.0] - 2026-10-25++### Added++- Initial explicitly registered TypeSafe System One provider using the released+ `typesafe-hs ^>=0.1.0.0` wire client. Strict request and response validation,+ atomic synthetic streaming, truthful call evidence, bounded HTTP calls and an+ opt-in live test. No retry policy is embedded.+- Installation, schema, refusal and consumer migration guide, plus CAP-27.++## [baikai-claude 0.7.2.0] - 2026-10-25++### Changed++- Require `baikai ^>=0.9.0`, encode user data as canonical JSON text, and refuse+ structured assistant history before dispatch with a typed field path.++### Fixed++- The offline cancellation fixture waits until the response reader is active+ before cancelling, eliminating a fixed-startup-delay race under build load.++## [baikai-openai 0.7.2.0] - 2026-10-25++### Changed++- Require `baikai ^>=0.9.0`, encode user data as canonical JSON text, and refuse+ structured assistant history before dispatch with a typed field path across+ Chat Completions, Responses and batch CLI calls.++### Fixed++- The offline cancellation fixture waits until the response reader is active+ before cancelling, eliminating a fixed-startup-delay race under build load.++## [baikai-trace-otel 0.4.1.0] - 2026-10-25++### Added++- Export structured answer data as canonical JSON under+ `baikai.response.structured_data`, on success and failure terminals.++### Changed++- Require `baikai ^>=0.9.0` for the expanded trace protocol.++## [baikai-effectful 0.4.0.4] - 2026-10-25++### Changed++- Require `baikai ^>=0.9.0`. Dependency-only update; no package API change.++## [baikai-kit 0.4.0.2] - 2026-10-25++### Changed++- Require `baikai ^>=0.9.0`. Dependency-only update; no package API change.++## [baikai-agent 0.2.0.3] - 2026-10-25++### Changed++- Require `baikai ^>=0.9.0`. Dependency-only update; no package API change.+ ## [baikai 0.8.0.0] - 2026-10-10 ### Added
baikai-trace-otel.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.4 name: baikai-trace-otel-version: 0.4.0.2+version: 0.4.1.0 synopsis: OpenTelemetry TraceSink for baikai. description: Provides an opt-in OpenTelemetry adapter for the baikai 'TraceSink'@@ -60,7 +60,7 @@ exposed-modules: Baikai.Trace.Sink.OpenTelemetry build-depends: aeson ^>=2.2,- baikai ^>=0.8.0,+ baikai ^>=0.9.0, base >=4.20 && <5, containers ^>=0.7, hs-opentelemetry-api >=1.0 && <1.1,@@ -83,7 +83,7 @@ build-depends: aeson ^>=2.2,- baikai ^>=0.8.0,+ baikai ^>=0.9.0, baikai-trace-otel, base, generic-lens,
src/Baikai/Trace/Sink/OpenTelemetry.hs view
@@ -146,7 +146,7 @@ -- evidence, and, because 'Otel.addAttributes' replaces an existing key -- and evidence is pushed before the terminal, overwrote the genuinely -- observed value on every call that did.- CallFinished {eventId, timestamp, latencyMs, inputTokens, outputTokens, usd, costBasis, usageAvailability} ->+ CallFinished {eventId, timestamp, latencyMs, inputTokens, outputTokens, usd, costBasis, usageAvailability, structuredData} -> case Map.lookup eventId m of -- A terminal without a live span is unreachable for normal withTraceStream -- usage because each traced call drives a fresh fold. Keep the silent drop so@@ -155,19 +155,20 @@ Just sp -> do let attrs :: HashMap.HashMap Text Attr.Attribute attrs =- maybe id (\n -> AttrMap.insertByKey SC.genAi_usage_inputTokens (fromIntegral n :: Int64)) inputTokens $- maybe id (\n -> AttrMap.insertByKey SC.genAi_usage_outputTokens (fromIntegral n :: Int64)) outputTokens $- maybe id (\b -> HashMap.insert "baikai.cost.basis" (Attr.toAttribute (Text.decodeUtf8 (Ev.canonicalEncode (Aeson.toJSON b))))) costBasis $- maybe id (\a -> HashMap.insert "baikai.usage.availability" (Attr.toAttribute (Text.decodeUtf8 (Ev.canonicalEncode (Aeson.toJSON a))))) usageAvailability $- maybe id (\s -> HashMap.insert "baikai.cost.usd" (Attr.toAttribute (Scientific.toRealFloat s :: Double))) usd $- HashMap.fromList- [ ("baikai.latency_ms", Attr.toAttribute latencyMs)- ]+ maybe id (\ds -> HashMap.insert "baikai.response.structured_data" (Attr.toAttribute (Text.decodeUtf8 (Ev.canonicalEncode (Aeson.toJSON ds))))) structuredData $+ maybe id (\n -> AttrMap.insertByKey SC.genAi_usage_inputTokens (fromIntegral n :: Int64)) inputTokens $+ maybe id (\n -> AttrMap.insertByKey SC.genAi_usage_outputTokens (fromIntegral n :: Int64)) outputTokens $+ maybe id (\b -> HashMap.insert "baikai.cost.basis" (Attr.toAttribute (Text.decodeUtf8 (Ev.canonicalEncode (Aeson.toJSON b))))) costBasis $+ maybe id (\a -> HashMap.insert "baikai.usage.availability" (Attr.toAttribute (Text.decodeUtf8 (Ev.canonicalEncode (Aeson.toJSON a))))) usageAvailability $+ maybe id (\s -> HashMap.insert "baikai.cost.usd" (Attr.toAttribute (Scientific.toRealFloat s :: Double))) usd $+ HashMap.fromList+ [ ("baikai.latency_ms", Attr.toAttribute latencyMs)+ ] Otel.addAttributes sp attrs Otel.setStatus sp Otel.Ok Otel.endSpan sp (Just (utcToTimestamp timestamp)) pure (Map.delete eventId m)- CallFailed {eventId, timestamp, latencyMs, errorMessage, inputTokens, outputTokens, usd, costBasis, usageAvailability} ->+ CallFailed {eventId, timestamp, latencyMs, errorMessage, inputTokens, outputTokens, usd, costBasis, usageAvailability, structuredData} -> case Map.lookup eventId m of -- A terminal without a live span is unreachable for normal withTraceStream -- usage because each traced call drives a fresh fold. Keep the silent drop so@@ -175,15 +176,16 @@ Nothing -> pure m Just sp -> do Otel.addAttributes sp $- maybe id (\n -> AttrMap.insertByKey SC.genAi_usage_inputTokens (fromIntegral n :: Int64)) inputTokens $- maybe id (\n -> AttrMap.insertByKey SC.genAi_usage_outputTokens (fromIntegral n :: Int64)) outputTokens $- maybe id (\s -> HashMap.insert "baikai.cost.usd" (Attr.toAttribute (Scientific.toRealFloat s :: Double))) usd $- maybe id (\b -> HashMap.insert "baikai.cost.basis" (Attr.toAttribute (Text.decodeUtf8 (Ev.canonicalEncode (Aeson.toJSON b))))) costBasis $- maybe id (\a -> HashMap.insert "baikai.usage.availability" (Attr.toAttribute (Text.decodeUtf8 (Ev.canonicalEncode (Aeson.toJSON a))))) usageAvailability $- HashMap.fromList- [ ("baikai.latency_ms", Attr.toAttribute latencyMs),- ("baikai.error", Attr.toAttribute errorMessage)- ]+ maybe id (\ds -> HashMap.insert "baikai.response.structured_data" (Attr.toAttribute (Text.decodeUtf8 (Ev.canonicalEncode (Aeson.toJSON ds))))) structuredData $+ maybe id (\n -> AttrMap.insertByKey SC.genAi_usage_inputTokens (fromIntegral n :: Int64)) inputTokens $+ maybe id (\n -> AttrMap.insertByKey SC.genAi_usage_outputTokens (fromIntegral n :: Int64)) outputTokens $+ maybe id (\s -> HashMap.insert "baikai.cost.usd" (Attr.toAttribute (Scientific.toRealFloat s :: Double))) usd $+ maybe id (\b -> HashMap.insert "baikai.cost.basis" (Attr.toAttribute (Text.decodeUtf8 (Ev.canonicalEncode (Aeson.toJSON b))))) costBasis $+ maybe id (\a -> HashMap.insert "baikai.usage.availability" (Attr.toAttribute (Text.decodeUtf8 (Ev.canonicalEncode (Aeson.toJSON a))))) usageAvailability $+ HashMap.fromList+ [ ("baikai.latency_ms", Attr.toAttribute latencyMs),+ ("baikai.error", Attr.toAttribute errorMessage)+ ] Otel.setStatus sp (Otel.Error errorMessage) Otel.endSpan sp (Just (utcToTimestamp timestamp)) pure (Map.delete eventId m)
test/Main.hs view
@@ -3,7 +3,7 @@ module Main (main) where import Baikai.Api (Api (..))-import Baikai.Content (AssistantContent (..), TextContent (..))+import Baikai.Content (AssistantContent (..), DataContent (..), TextContent (..)) import Baikai.Context (Context (..), emptyContext) import Baikai.Cost qualified as Cost import Baikai.Error (BaikaiError, providerError)@@ -37,6 +37,7 @@ import Control.Concurrent (threadDelay) import Control.Exception (throwIO) import Control.Lens ((&), (.~), (^.))+import Control.Monad (forM_) import Data.Aeson qualified as Aeson import Data.Generics.Labels () import Data.HashMap.Strict qualified as HashMap@@ -62,7 +63,8 @@ defaultMain $ testGroup "baikai-trace-otel"- [ successSpanTest,+ [ structuredDataSpanTest,+ successSpanTest, observedModelSpanTest, failureSpanTest, partialBillingSpanTest,@@ -550,3 +552,22 @@ "gen_ai.response.model must be absent when observedModel is Unobserved" (not (HashMap.member "gen_ai.response.model" attrs)) _ -> assertFailure "expected exactly one span"++structuredDataSpanTest :: TestTree+structuredDataSpanTest = testCase "structured data reaches the actual OTel exporter without evidence opt-in" $+ forM_ [False, True] $ \failed -> do+ let a = Custom (if failed then "baikai-otel-failed-data" else "baikai-otel-data")+ dat = DataContent (Aeson.object ["urgent" Aeson..= True]) Nothing Nothing (Just "provider_classification")+ response = stubResponse a & #message . #content .~ V.singleton (AssistantData dat)+ handler _ _ _ = pure (if failed then response & #message . #stopReason .~ ErrorReason & #errorInfo .~ Just (providerError "failed") else response)+ registerApiProvider (apiProviderWith a (liftCompleteToStream handler) handler)+ (tracer, getSpans) <- newTracerWithInMemory+ _ <- withTrace (otelSink tracer) (stubModel a) stubContext stubOptions+ spans <- getSpans+ case spans of+ [sp] -> do+ hot <- spanHotSnapshot sp+ let attrs = Attr.getAttributeMap (Otel.hotAttributes hot)+ HashMap.lookup "baikai.response.structured_data" attrs+ @?= Just (Attr.toAttribute (TextEncoding.decodeUtf8 (canonicalEncode (Aeson.toJSON (V.singleton dat)))))+ _ -> assertFailure ("expected one span, got " <> show (length spans))