diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9b1357..f7b85e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,7 @@ jobs: working-directory: agenttel-go run: go test -race -count=1 -timeout=120s ./... + # agenttel-go/go.work links the GenAI submodules to this checkout's agenttel-go - name: Test GenAI Anthropic working-directory: agenttel-go/genai/anthropic run: go test -race -count=1 -timeout=60s ./... @@ -63,6 +64,16 @@ jobs: working-directory: agenttel-go/genai/openai run: go test -race -count=1 -timeout=60s ./... + # Build the submodules exactly as consumers do: against the agenttel-go + # release their go.mod pins (no workspace, no replace) + - name: GenAI submodules against released agenttel-go + working-directory: agenttel-go + env: + GOWORK: 'off' + run: | + (cd genai/anthropic && go mod verify && go vet ./... && go test -count=1 ./...) + (cd genai/openai && go mod verify && go vet ./... && go test -count=1 ./...) + - name: Vet working-directory: agenttel-go run: go vet ./... diff --git a/CHANGELOG.md b/CHANGELOG.md index a82410c..a3d7cd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ All SDKs now accept the Spring Boot dialect: - **Added:** net/http middleware sets decision attributes and, for Go 1.22+ `ServeMux` patterns, records `http.route` and matches `GET /items/{id}` after routing. - **Fixed:** `examples/go-service-example` and `examples/gin-example` compile and run against the real API (`agenttel.New().WithConfigFile(...).Build()`, `engine.SpanProcessor()`, `otelhttp` / `otelgin`). - **Docs:** minimum versions are Go 1.24 and `go.opentelemetry.io/otel/sdk` v1.39.0 (previously documented as 1.22 / 1.33.0). +- **GenAI submodules released:** `go.agenttel.dev/agenttel-go/genai/openai` and `.../genai/anthropic` are tagged `v0.4.0-alpha`. They now require the published `agenttel-go v0.4.0-alpha`; previously they pinned an unpublished `v0.1.0` plus a `replace` and could not be installed. In-repo development uses `agenttel-go/go.work`, and CI also builds them without it, the same way consumers do. - **Breaking (config struct only):** YAML tags on `AgentTelConfig` are now kebab-case. camelCase files still load, with a warning. ### JVM diff --git a/agenttel-go/genai/anthropic/example_test.go b/agenttel-go/genai/anthropic/example_test.go new file mode 100644 index 0000000..eb7a1ae --- /dev/null +++ b/agenttel-go/genai/anthropic/example_test.go @@ -0,0 +1,22 @@ +package anthropic_test + +import ( + "context" + + "github.com/anthropics/anthropic-sdk-go" + + agenttelanthropic "go.agenttel.dev/agenttel-go/genai/anthropic" +) + +// Wrap the Anthropic Messages service; every message call becomes a gen_ai.* +// span with token usage and agenttel.genai.cost_usd. +func ExampleWrap() { + client := anthropic.NewClient() + traced := agenttelanthropic.Wrap(&client.Messages) + + _, _ = traced.CreateMessage(context.Background(), anthropic.MessageNewParams{ + Model: anthropic.ModelClaudeSonnet4_5, + MaxTokens: 1024, + Messages: []anthropic.MessageParam{anthropic.NewUserMessage(anthropic.NewTextBlock("Hello"))}, + }) +} diff --git a/agenttel-go/genai/anthropic/go.mod b/agenttel-go/genai/anthropic/go.mod index 4a7877a..37fe73b 100644 --- a/agenttel-go/genai/anthropic/go.mod +++ b/agenttel-go/genai/anthropic/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.13 require ( github.com/anthropics/anthropic-sdk-go v1.27.1 - go.agenttel.dev/agenttel-go v0.1.0 + go.agenttel.dev/agenttel-go v0.4.0-alpha go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 @@ -23,8 +23,6 @@ require ( github.com/tidwall/sjson v1.2.5 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect - golang.org/x/sync v0.16.0 // indirect + golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.39.0 // indirect ) - -replace go.agenttel.dev/agenttel-go => ../../ diff --git a/agenttel-go/genai/anthropic/go.sum b/agenttel-go/genai/anthropic/go.sum index da49fe9..94a8fca 100644 --- a/agenttel-go/genai/anthropic/go.sum +++ b/agenttel-go/genai/anthropic/go.sum @@ -29,6 +29,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.agenttel.dev/agenttel-go v0.4.0-alpha h1:xoHcxGouP+ZZYHR5rEMxDc/cwtcOTxyJYh0S3LHW7is= +go.agenttel.dev/agenttel-go v0.4.0-alpha/go.mod h1:bxtOzraHvRCDnQb19XzbsGOcU8NCj5maDpzYdPEbhjY= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= @@ -43,8 +45,8 @@ go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6 go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= diff --git a/agenttel-go/genai/openai/example_test.go b/agenttel-go/genai/openai/example_test.go new file mode 100644 index 0000000..3f482b5 --- /dev/null +++ b/agenttel-go/genai/openai/example_test.go @@ -0,0 +1,21 @@ +package openai_test + +import ( + "context" + + goopenai "github.com/sashabaranov/go-openai" + + agenttelopenai "go.agenttel.dev/agenttel-go/genai/openai" +) + +// Wrap a go-openai client; every chat completion becomes a gen_ai.* span with +// token usage and agenttel.genai.cost_usd. +func ExampleWrap() { + client := goopenai.NewClient("sk-...") + traced := agenttelopenai.Wrap(client) + + _, _ = traced.CreateChatCompletion(context.Background(), goopenai.ChatCompletionRequest{ + Model: goopenai.GPT4o, + Messages: []goopenai.ChatCompletionMessage{{Role: goopenai.ChatMessageRoleUser, Content: "Hello"}}, + }) +} diff --git a/agenttel-go/genai/openai/go.mod b/agenttel-go/genai/openai/go.mod index a76a4eb..7104b56 100644 --- a/agenttel-go/genai/openai/go.mod +++ b/agenttel-go/genai/openai/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.13 require ( github.com/sashabaranov/go-openai v1.36.1 - go.agenttel.dev/agenttel-go v0.1.0 + go.agenttel.dev/agenttel-go v0.4.0-alpha go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 @@ -21,5 +21,3 @@ require ( go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect ) - -replace go.agenttel.dev/agenttel-go => ../../ diff --git a/agenttel-go/genai/openai/go.sum b/agenttel-go/genai/openai/go.sum index 461f5b3..63a695f 100644 --- a/agenttel-go/genai/openai/go.sum +++ b/agenttel-go/genai/openai/go.sum @@ -17,6 +17,8 @@ github.com/sashabaranov/go-openai v1.36.1 h1:EVfRXwIlW2rUzpx6vR+aeIKCK/xylSrVYAx github.com/sashabaranov/go-openai v1.36.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.agenttel.dev/agenttel-go v0.4.0-alpha h1:xoHcxGouP+ZZYHR5rEMxDc/cwtcOTxyJYh0S3LHW7is= +go.agenttel.dev/agenttel-go v0.4.0-alpha/go.mod h1:bxtOzraHvRCDnQb19XzbsGOcU8NCj5maDpzYdPEbhjY= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= diff --git a/agenttel-go/go.work b/agenttel-go/go.work new file mode 100644 index 0000000..6655c3f --- /dev/null +++ b/agenttel-go/go.work @@ -0,0 +1,10 @@ +// Local development workspace: the GenAI submodules build against this +// checkout's agenttel-go instead of the released version their go.mod pins. +// Set GOWORK=off to build them exactly as consumers do. +go 1.24.0 + +use ( + . + ./genai/anthropic + ./genai/openai +) diff --git a/agenttel-go/go.work.sum b/agenttel-go/go.work.sum new file mode 100644 index 0000000..3a09b5e --- /dev/null +++ b/agenttel-go/go.work.sum @@ -0,0 +1 @@ +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index 8b19a71..5744a3b 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -334,15 +334,48 @@ HTTP server spans get topology, static baselines and decision attributes (`retry ### 4. GenAI Instrumentation (Optional) -```go -import "go.agenttel.dev/agenttel-go/genai" +The OpenAI and Anthropic wrappers are separate Go modules, so the core SDK doesn't pull in either vendor SDK: -builder := genai.NewSpanBuilder(tracer) -ctx, span := builder.StartChatSpan(ctx, "gpt-4o", "openai") -// ... make LLM call ... -genai.EndSpanSuccess(span, promptTokens, completionTokens, "gpt-4o") +```bash +go get go.agenttel.dev/agenttel-go/genai/openai@v0.4.0-alpha # github.com/sashabaranov/go-openai +go get go.agenttel.dev/agenttel-go/genai/anthropic@v0.4.0-alpha # github.com/anthropics/anthropic-sdk-go ``` +=== "OpenAI" + + ```go + import agenttelopenai "go.agenttel.dev/agenttel-go/genai/openai" + + traced := agenttelopenai.Wrap(openai.NewClient(apiKey)) + resp, err := traced.CreateChatCompletion(ctx, req) // also CreateChatCompletionStream + ``` + +=== "Anthropic" + + ```go + import agenttelanthropic "go.agenttel.dev/agenttel-go/genai/anthropic" + + client := anthropic.NewClient() + traced := agenttelanthropic.Wrap(&client.Messages) + msg, err := traced.CreateMessage(ctx, params) // also CreateMessageStreaming + ``` + +=== "Any other LLM client" + + ```go + import "go.agenttel.dev/agenttel-go/genai" + + ctx, span := genai.NewSpanBuilder(tracer).StartChatSpan(ctx, "gpt-4o", "openai") + // ... make the LLM call ... + genai.EndSpanSuccess(span, genai.ChatResult{ + ResponseModel: "gpt-4o", + InputTokens: promptTokens, + OutputTokens: completionTokens, + }) + ``` + +Each call becomes a `gen_ai.*` span with token usage and `agenttel.genai.cost_usd`. + ### 5. Agentic Observability (Optional) ```go