packages feed

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 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))