Skip to content

feat(go): Go SDK with OpenAI instrumentor - #166

Open
pmady wants to merge 6 commits into
future-agi:mainfrom
pmady:feat/go-sdk
Open

feat(go): Go SDK with OpenAI instrumentor#166
pmady wants to merge 6 commits into
future-agi:mainfrom
pmady:feat/go-sdk

Conversation

@pmady

@pmady pmady commented May 6, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a Go SDK for traceAI. Two modules:

  • go/traceai — core setup (TracerProvider config, GenAI semconv keys, env-based defaults)
  • go/traceai_openai — HTTP middleware for the openai-go client that captures chat/completion/embedding calls as OTel spans

Why?

Go SDK was listed on the roadmap (#164 finding 7). Lots of Go services calling LLM APIs directly or through gateways, and there was no Go instrumentation path.

How was it tested?

  • go test ./... passes in both modules (4 tests covering happy path, passthrough, error, content capture toggle)
  • go vet ./... clean
  • Integration tests pass (or N/A — no live API calls in tests)

Checklist

  • Branch is off main
  • Commit messages follow Conventional Commits
  • No TODOs or commented-out code
  • No API keys or secrets in the diff
  • README included

Notes for reviewers

5 commits over ~9 days. The middleware operates on net/http types so it doesn't pull in the openai-go library as a dependency — works with any HTTP client that hits the OpenAI API path patterns.

Related: #164, #165

pmady added 5 commits April 28, 2026 16:25
Scaffold the Go SDK with OTel TracerProvider setup, GenAI
semantic convention keys, and the Instrumentor interface.

Signed-off-by: pmady <pavan4devops@gmail.com>
HTTP middleware that intercepts chat/completion/embedding
calls and records them as OTel spans with GenAI semconv
attributes.

Signed-off-by: pmady <pavan4devops@gmail.com>
Covers chat completion happy path, non-AI endpoint passthrough,
error response handling, and content capture toggle.

Signed-off-by: pmady <pavan4devops@gmail.com>
Signed-off-by: pmady <pavan4devops@gmail.com>
Signed-off-by: pmady <pavan4devops@gmail.com>
@pmady

pmady commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

this covers finding 7 from #164. middleware approach means no hard dep on the openai-go library -- any HTTP client hitting their API paths gets traced.

the module structure mirrors what python does (core + per-framework packages) so adding anthropic/cohere later is straightforward.

cc @niaborowy @JayaSurya-27

@pmady

pmady commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

@JayaSurya-27 any thoughts on the go sdk? lmk if the approach works or if you'd rather do it differently

@abhijaisrivastava15
abhijaisrivastava15 requested review from JayaSurya-27 and removed request for atharva-bhange June 24, 2026 13:55
@pmady

pmady commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Hi team, just checking in .Happy to address any feedback on this PR.
Let me know if there's anything you'd like adjusted.
@JayaSurya-27 @nik13

@abhijaisrivastava15 abhijaisrivastava15 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @pmady,

Thank you for contributing to Future AGI.

I read through the whole thing. The layout mirrors the Python side (core package plus a per-framework package), the HTTP exporter points at the right ingest path, you pinned semconv/v1.26.0 so it matches the SDK's default resource schema and Register does not error on a schema conflict, and the request-parameter and token attributes use the canonical gen_ai.* keys. That is a solid base to build on.

This is a whole new language SDK though, so before we can move it forward two things need to happen: a maintainer needs to own the decision to ship a Go SDK now (the roadmap note in #164 is a pointer, not a sign-off), and the span schema needs to match what our platform actually ingests, because right now several spans will be exported but will not map. Concretely:

  1. Content keys. You emit gen_ai.prompt and gen_ai.completion. Our canonical schema keys prompt and response content off gen_ai.input.messages.<i>.role/.content and gen_ai.output.messages, plus input.value / output.value for the raw blob (see the Python traceai_openai extractor). As written, the prompt and completion text will not land in the platform's input/output, and evals that read those will not bind.

  2. Span kind. The spans never set gen_ai.span.kind. The Python instrumentors set it to LLM, and the platform uses it to classify a span as an LLM call. Without it these render as generic spans. Please set it, and confirm in the UI that a real trace shows up as an LLM call.

  3. The content-capture toggle drops too much. With WithContentCapture(false) the middleware skips both the request and response bodies entirely, so you also lose the model name, token usage, response id and finish reasons, not just the prompt and completion text. Those are not PII. Please gate only gen_ai.prompt / gen_ai.completion behind the toggle and always capture model and usage.

  4. Streaming. For a streamed call the response body is an SSE stream. io.ReadAll buffers the whole stream before returning it, which defeats streaming for the caller, and the SSE text is not a single JSON object so extractResponseAttributes fails and tokens, response model and completion are silently dropped. Please detect the streaming case and handle it, or at minimum skip the read so you do not break the caller's stream.

  5. gRPC transport is misconfigured. WithEndpoint expects host:port with no scheme (example.com:4317), but you pass https://grpc.futureagi.com. Use WithEndpointURL for a full URL, or grpc.futureagi.com:443 with WithEndpoint. As is, the gRPC path will not connect, and config.go has no test so nothing catches it.

  6. The module is not consumable as published. traceai_openai/go.mod requires traceai v0.0.0 with a local replace ../traceai. replace is ignored for anyone importing your module, and v0.0.0 is not a real tag, so go get of the openai package will fail outside this repo. This needs a real tagged version of the core module and a require that points at it.

On tests: the core traceai module has none, so go test ./... passes there only because there is nothing to run. Please add a test around exporter and endpoint construction to pin the transport and header wiring, and add streaming and content-capture-off cases to the middleware tests so the model and token attributes are asserted.

Smaller things, non-blocking: gen_ai.operation.name uses completion and embedding where the semconv values are text_completion and embeddings; gen_ai.request.duration_ms duplicates the span duration; and the Instrumentor interface and Tracer helper in instrumentor.go are unused, with Tracer calling the deprecated trace.NewNoopTracerProvider.

Happy to do a full pass once the span schema matches the platform and the module is go-gettable.

Thank you

pmady added a commit to pmady/traceAI that referenced this pull request Aug 25, 2026
Addresses review feedback on future-agi#166.

- set gen_ai.span.kind=LLM so the platform classifies these as LLM calls
- WithContentCapture(false) now gates only gen_ai.prompt/completion; model,
  token usage, response id and finish reasons are always recorded
- gRPC exporter uses WithEndpointURL, since FI_GRPC_URL carries a scheme and
  WithEndpoint expects host:port
- gen_ai.operation.name uses the semconv values text_completion and embeddings
- drop gen_ai.request.duration_ms, which duplicated the span duration
- delete the unused Instrumentor interface and Tracer helper; Tracer returned a
  noop provider despite its comment claiming it fell back to the global one
- add exporter tests covering the ingest path, auth headers and the gRPC URL
- assert model and usage survive content capture being turned off
- gofmt config.go and semconv.go

Still open, pending your call: the input/output content keys (1), streaming (4)
and tagging the core module (6).
Sets gen_ai.span.kind so spans classify as LLM calls, scopes
WithContentCapture to prompt/completion only so model and usage
survive it, and switches the grpc exporter to WithEndpointURL.

Adds exporter tests for the ingest path and auth headers. Drops the
unused Instrumentor interface and Tracer helper.

Signed-off-by: pmady <pavan4devops@gmail.com>
@pmady

pmady commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

@abhijaisrivastava15
pushed fbc3b3f.

  1. done, sets gen_ai.span.kind=LLM, test asserts it. haven't confirmed in the UI yet, will do that once i can point a real trace at a project.
  2. done, the toggle now gates only gen_ai.prompt/completion; model, usage, response id and finish reasons are always recorded, and the content-capture-off test asserts they survive.
  3. done, switched to WithEndpointURL, which is what the http path already used. added a test that builds the grpc exporter off the default scheme'd url so this can't regress.
    tests. added config_test.go, an httptest server asserting the ingest path /tracer/v1/traces and both auth headers, plus env/default config wiring.
    smaller. text_completion/embeddings now, dropped gen_ai.request.duration_ms, deleted instrumentor.go (its Tracer helper returned a noop provider even though the comment claimed it fell back to the global one), and gofmt'd config.go + semconv.go.

three i'd rather not guess at:

  1. content keys: happy to move to gen_ai.input.messages..role/.content and input.value/output.value. can you point me at the exact python extractor so i match the indexing and serialization rather than approximating it?
  2. streaming: do you want SSE parsed so tokens and completion still land, or just detected and passed through untouched so we stop buffering the caller's stream? second is much smaller if you only care about not breaking streaming.
  3. module: needs a real tag on go/traceai before the replace can become a require. that's a maintainer action i can't do from a fork, so tell me when there's a version to point at.

and the bigger one: who owns the ship/don't-ship call on a go sdk? happy to keep going, just don't want to keep pushing if there's no appetite for it in-tree yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants