Skip to content

chore(sdk): migrate legacy rhesis.sdk.telemetry imports to rhesis.telemetry - #2490

Merged
Arman-Beykmohammadi merged 6 commits into
mainfrom
chore/telemetry-import-migration
Aug 18, 2026
Merged

chore(sdk): migrate legacy rhesis.sdk.telemetry imports to rhesis.telemetry#2490
Arman-Beykmohammadi merged 6 commits into
mainfrom
chore/telemetry-import-migration

Conversation

@Arman-Beykmohammadi

Copy link
Copy Markdown
Collaborator

Closes #2473

What

Point every telemetry import site in this repository at the canonical
rhesis.telemetry.* modules, keep the rhesis.sdk.telemetry.* shims for outside consumers, and give
the light package the py.typed marker it turns out to need.

Why

#2462 moved attributes, context and token_extraction into the light rhesis package so
framework integrations can depend on rhesis[telemetry] instead of the whole SDK, and left
re-export shims behind. Every call site here still went through the shims, which made the canonical
path the exception and kept the old ones looking load-bearing.

Changes

chore(sdk): import telemetry primitives from rhesis.telemetry — 43 files across sdk/,
apps/backend/, tests/, agents/, examples/ and docs/. The path flattens on the way:
utils.token_extractiontoken_extraction. Two mock targets in
tests/backend/tasks/architect/test_chat.py move with the lazy import they patch — a mock on the
shim would no longer be seen by code importing from the canonical module.

The grep from the issue now returns exactly one line, in sdk/src/rhesis/sdk/telemetry/utils/__init__.py,
which keeps re-exporting extract_token_usage through the shim deliberately: that package is itself
the old public surface, and there is now a comment saying so. The shim docstrings say they exist for
consumers outside this repository rather than counting internal call sites.

test(sdk): pin the legacy telemetry import shims — nothing here exercises the old paths any
more, and a compatibility surface no test touches is one that breaks silently. Released
rhesis-haystack versions import two of them. The test asserts that each advertised name is the
canonical object rather than a copy, that a public symbol added to a canonical module must be
forwarded, and that the ContextVars are shared across both paths — that identity is what lets the SDK
and a framework integration read the same turn state while importing from different places.

fix(telemetry): ship a py.typed marker in the light package — not in the issue, found while
doing the matching swap in the Haystack integration. rhesis-sdk ships rhesis/py.typed at the
namespace root, and that marker is what makes type checkers accept rhesis.telemetry.*. Depend on
rhesis[telemetry] without the SDK and there is no marker, so a type checker skips those imports:
the integration's mypy run reports 11 import-untyped errors on fully annotated modules the moment
it drops rhesis-sdk. Confirmed present in the built wheel.

Testing

  • make -C sdk test — 2429 passed, 16 skipped
  • tests/backend/{services/architect,tasks,services/telemetry} — 574 passed, 3 skipped
  • agents/visit-prep and agents/travel-agent span-tree tests — 5 and 3 passed
  • ruff check and format clean on every touched file

Note for the reviewer

The py.typed commit is what unblocks
deepset-ai/haystack-core-integrations#3669,
which now depends on rhesis[telemetry]>=0.13.0 instead of rhesis-sdk. That integration needs a
release of the light package carrying both this marker and the modules #2462 moved — published
rhesis 0.12.0 predates the move.

PR #2462 moved attributes, context and token_extraction into the light
rhesis package so framework integrations can depend on rhesis[telemetry]
rather than the whole SDK, and left re-export shims at the old
rhesis.sdk.telemetry paths. Every import site in this repository still went
through those shims, which made the canonical path the exception rather
than the rule and kept the old ones looking load-bearing.

Point them at the canonical modules: rhesis.telemetry.attributes,
rhesis.telemetry.context and rhesis.telemetry.token_extraction. Note the
path flattens — utils.token_extraction becomes token_extraction. Two patch
targets in tests/backend/tasks/architect/test_chat.py move with the lazy
import they patch, since a mock on the shim would no longer be seen by code
importing from the canonical module.

The shims stay, for consumers outside this repository — released
rhesis-haystack versions import them — and their docstrings now say that
rather than counting internal call sites. rhesis.sdk.telemetry.utils keeps
re-exporting extract_token_usage through the shim on purpose: that package
is itself the old public surface.

Closes #2473

Signed-off-by: Arman Beykmohammadi <arman.beykmohammadi@rhesis.ai>
Nothing in this repository imports rhesis.sdk.telemetry.attributes,
.context or .utils.token_extraction any more, so the shims that keep them
working are now untested — a compatibility surface no test touches is one
that breaks silently. Released rhesis-haystack versions import two of them.

Assert what the shims actually promise: every name they advertise is the
canonical object rather than a copy, a public symbol added to a canonical
module has to be forwarded, and the ContextVars are shared across both
paths, since that identity is what lets the SDK and a framework integration
read the same turn state while importing from different places.

Signed-off-by: Arman Beykmohammadi <arman.beykmohammadi@rhesis.ai>
rhesis-sdk ships rhesis/py.typed at the namespace root, which is what makes
type checkers accept rhesis.telemetry.* — including the modules that now
live in the light package. Anything depending on rhesis[telemetry] without
the SDK gets no marker, so a type checker skips those imports entirely: the
Haystack integration's mypy run reports 11 import-untyped errors the moment
it drops rhesis-sdk, on modules that are fully annotated.

The package ships annotations, so declare it typed. The file sits beside
rhesis/__init__.py, matching where rhesis-sdk puts its own — both
distributions already contribute files at that path, so this adds no new
kind of overlap. Confirmed present in the built wheel.

Signed-off-by: Arman Beykmohammadi <arman.beykmohammadi@rhesis.ai>

@peqy peqy Bot 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.

[Improvement] The new legacy-shim tests are a great safety net. One tweak: test_shim_forwards_everything_public currently ignores public primitive constants in the canonical modules (since they don’t have __module__), so a future constant could be added without the test forcing it into the shim.

Everything else in this PR looks consistent with the migration goal, and adding py.typed to the light package should unblock downstream type checking.

Found 1 issue (0 critical, 1 improvement).

Comment thread tests/sdk/telemetry/test_legacy_import_paths.py Outdated
@Arman-Beykmohammadi
Arman-Beykmohammadi force-pushed the chore/telemetry-import-migration branch from c9df430 to 32bfc6a Compare August 14, 2026 11:36

@peqy peqy Bot 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.

Overall this looks good: the repo now consistently imports telemetry from rhesis.telemetry.*, the shim modules are clearly documented as legacy-only, and adding packages/rhesis/src/rhesis/py.typed should fix downstream type checking.

Only remaining item is the legacy-shim pinning test: test_shim_forwards_everything_public currently misses in-module primitive constants (e.g. MAX_CONTENT_LENGTH in rhesis.telemetry.attributes), so the shim surface could still drift silently. See thread for suggested fixes.

The drift check compared the shim against public names inferred from the
canonical module's namespace, filtered by __module__. That filter catches
functions and classes and silently drops constants: MAX_CONTENT_LENGTH is
an int and carries no __module__, so the check was blind to exactly the
kind of symbol most likely to be added later — and dropping the filter
instead would have swept in imports the module makes for its own use, like
FORBIDDEN_SPAN_DOMAINS from schemas. Inference cannot tell those apart.

Declare the surface instead: __all__ on rhesis.telemetry.attributes,
.context and .token_extraction, which the shims must now mirror exactly.
Both directions are asserted, so a name added to a canonical module fails
the test until someone decides whether it belongs on the old path too.

Verified the new check bites where the old one did not: appending a
constant to a canonical __all__ fails it, while the old heuristic did not
even see MAX_CONTENT_LENGTH.

Signed-off-by: Arman Beykmohammadi <arman.beykmohammadi@rhesis.ai>
@peqy

peqy Bot commented Aug 17, 2026

Copy link
Copy Markdown

Looks good overall; the new canonical __all__ + shim-surface test is a solid way to pin the compatibility layer.

[Improvement] packages/rhesis/src/rhesis/py.typed may not actually ship in the rhesis wheel without explicit build include

Fix: mirror the SDK’s approach by adding something like [tool.hatch.build.targets.wheel].include = ["src/rhesis/py.typed"] (and sdist if needed) in packages/rhesis/pyproject.toml, unless you’ve confirmed hatchling already includes it by default.

[Improvement] One internal legacy import still appears to remain

Fix: agents/travel-agent/src/travel_agent/session.py still imports get_conversation_id/set_conversation_id from rhesis.sdk.telemetry.context; switching to rhesis.telemetry.context would make the “all internal call sites use canonical paths” goal fully true.

Found 2 issues (0 critical, 2 improvements).

Missed by the sweep in the first commit: that pass rewrote the working
tree, and this file already carried the canonical import there as
uncommitted work, so the grep came back clean while the committed version
still went through the shim.

Signed-off-by: Arman Beykmohammadi <arman.beykmohammadi@rhesis.ai>
@Arman-Beykmohammadi

Copy link
Copy Markdown
Collaborator Author

Thanks — one real, one already covered.

The travel-agent import: you are right, fixed in a6bbb9e. Worth recording why it slipped, because the reason is the interesting part: my sweep rewrote the working tree, and that file has uncommitted work of mine in which the import was already canonical. So the grep came back clean while the committed version still went through the shim. Re-ran the check against committed content this time (git grep ... <branch>), and session.py was the only remaining site — everything else the pattern matches is deliberate: the comments in the canonical modules that point at their shims, and test_legacy_import_paths.py, which has to import the legacy paths to test them.

py.typed: confirmed shipping, no build change needed. I checked the artifacts rather than the config, since that is the only thing that settles it. uv build on packages/rhesis produces:

  • wheel → contains rhesis/py.typed
  • sdist → contains rhesis-<version>/src/rhesis/py.typed, and the wheel in that run was built from the sdist, so the whole chain carries it

Independently: mypy on the Haystack integration passes against a wheel install of this package in a clean venv (not an editable install), which it cannot do unless the marker is really in the wheel.

hatchling includes non-Python files under packages = ["src/rhesis"] by default, which is why it works. I would rather not add an explicit include on top: alongside packages it is redundant at best, and an include allowlist can narrow what ends up in the wheel, so it is a real risk taken against a problem that measurement says is not there.

@peqy peqy Bot 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.

Looks good; the canonical __all__ + shim-surface test is a solid way to pin the compatibility layer, and the last internal legacy import in travel-agent is now migrated.

One remaining (non-blocking) suggestion from my earlier note: consider explicitly including src/rhesis/py.typed in packages/rhesis wheel build config as a belt-and-suspenders measure (mirrors the SDK’s hatch config), unless you’re 100% confident hatchling is already packaging it by default.

hatchling already ships the marker: with packages = ["src/rhesis"] it takes
everything under that directory, and both artifacts carry it — the wheel as
rhesis/py.typed, the sdist as src/rhesis/py.typed. Building wheel and sdist
with and without this line produces identical file sets, 14 and 16 entries
either way, so this changes no output today.

What it buys is that the marker stops depending on that default. An exclude
pattern or a narrowed packages list would silently drop it again, and the
only symptom would be a downstream type checker quietly skipping
rhesis.telemetry.* — which is exactly the failure this marker was added to
fix. The SDK's wheel config lists its py.typed the same way, so this also
makes the two packages read alike.

Signed-off-by: Arman Beykmohammadi <arman.beykmohammadi@rhesis.ai>
@peqy

peqy Bot commented Aug 18, 2026

Copy link
Copy Markdown

Follow-up on latest commits:

  • packages/rhesis/pyproject.toml now explicitly includes src/rhesis/py.typed in the wheel build.
  • ✅ travel-agent’s last internal legacy import was switched to rhesis.telemetry.context.

No further issues from my side.

@Arman-Beykmohammadi

Copy link
Copy Markdown
Collaborator Author

Took the py.typed suggestion after all — 8d701833c.

I wanted to settle the "unless you are 100% confident" part with a measurement rather than an argument, so I built wheel and sdist both ways and compared file sets: identical, 14 entries in the wheel and 15 in the sdist either way, marker present in both. So my earlier worry that an include allowlist alongside packages could narrow the wheel does not hold for hatchling — it merges rather than replaces, which the SDK's own config already demonstrated.

That makes the line free, and it buys something real: the marker stops depending on the packages default. A later exclude pattern or a narrowed packages list would drop it again, and the only symptom would be a downstream type checker quietly skipping rhesis.telemetry.* — the exact failure the marker exists to prevent. It also makes this package's wheel config read like the SDK's.

Both of your notes from that review are now closed. Full suites re-run on the final state: 2429 sdk tests, 574 backend tests, all green.

@Arman-Beykmohammadi
Arman-Beykmohammadi merged commit 07b4865 into main Aug 18, 2026
19 checks passed
@Arman-Beykmohammadi
Arman-Beykmohammadi deleted the chore/telemetry-import-migration branch August 18, 2026 08:21
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.

chore(sdk): migrate legacy rhesis.sdk.telemetry imports to rhesis.telemetry

1 participant