fix: restore get_analytics() on Session and refresh env vars on init#1311
fix: restore get_analytics() on Session and refresh env vars on init#1311AmaTsumeAkira wants to merge 1 commit intoAgentOps-AI:mainfrom
Conversation
- Add get_analytics() method to Session class for backward compatibility with older AgentOps versions (Fixes AgentOps-AI#929) - Add refresh_from_env() to Config class to re-read env vars on init - Call refresh_from_env() in Client.init() so env vars set after import are properly picked up (Fixes AgentOps-AI#962)
There was a problem hiding this comment.
Pull request overview
Restores backward compatibility for older AgentOps integrations by reintroducing Session.get_analytics() and ensuring configuration can reflect environment variables at initialization time.
Changes:
- Added
Session.get_analytics()to legacy Session to expose token/cost analytics from span attributes. - Added
Config.refresh_from_env()to re-read configuration values from environment variables. - Called
refresh_from_env()duringClient.init()before applying explicit kwargs.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
agentops/legacy/__init__.py |
Adds Session.get_analytics() for legacy integrations to read token/cost metrics from OTel span attributes. |
agentops/config.py |
Adds Config.refresh_from_env() to refresh config values from env vars. |
agentops/client/client.py |
Invokes refresh_from_env() during client initialization. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if attrs: | ||
| analytics["prompt_tokens"] = int(attrs.get("gen_ai.usage.prompt_tokens", 0) or 0) | ||
| analytics["completion_tokens"] = int(attrs.get("gen_ai.usage.completion_tokens", 0) or 0) | ||
| analytics["total_tokens"] = analytics["prompt_tokens"] + analytics["completion_tokens"] |
There was a problem hiding this comment.
total_tokens is derived as prompt_tokens + completion_tokens, but spans in this codebase can also set gen_ai.usage.total_tokens directly (and some may not populate prompt/completion). Prefer reading gen_ai.usage.total_tokens when present and only falling back to a sum, otherwise get_analytics() can incorrectly report 0 tokens even when total tokens are recorded on the span.
| analytics["total_tokens"] = analytics["prompt_tokens"] + analytics["completion_tokens"] | |
| total_tokens_attr = attrs.get("gen_ai.usage.total_tokens") | |
| if total_tokens_attr is not None: | |
| analytics["total_tokens"] = int(total_tokens_attr or 0) | |
| else: | |
| analytics["total_tokens"] = analytics["prompt_tokens"] + analytics["completion_tokens"] |
| if hasattr(span, "_attributes"): | ||
| attrs = span._attributes | ||
| elif hasattr(span, "attributes"): | ||
| attrs = span.attributes |
There was a problem hiding this comment.
This reads OpenTelemetry attributes via the private span._attributes field. To avoid SDK-version coupling, prefer the public API (e.g., span.attributes) and only use private fields as a last resort with a clear compatibility comment.
| if hasattr(span, "_attributes"): | |
| attrs = span._attributes | |
| elif hasattr(span, "attributes"): | |
| attrs = span.attributes | |
| if hasattr(span, "attributes"): | |
| # Prefer public OpenTelemetry API to avoid SDK-version coupling | |
| attrs = span.attributes | |
| elif hasattr(span, "_attributes"): | |
| # Backwards compatibility: some OpenTelemetry SDK versions expose attributes via _attributes | |
| attrs = span._attributes |
| """Re-read all configuration values from environment variables. | ||
|
|
||
| This allows configuration to be updated after import by setting | ||
| environment variables and calling init() or configure(). |
There was a problem hiding this comment.
The docstring says env vars can be updated by calling configure(), but agentops.configure()/Client.configure() ultimately call Config.configure() and do not invoke refresh_from_env(). Either update this docstring to only mention init(), or ensure configure() triggers an env refresh when that’s intended.
| environment variables and calling init() or configure(). | |
| environment variables and calling init(). |
| def refresh_from_env(self) -> None: | ||
| """Re-read all configuration values from environment variables. | ||
|
|
||
| This allows configuration to be updated after import by setting | ||
| environment variables and calling init() or configure(). | ||
| Values explicitly set via parameters take precedence over env vars. | ||
| """ | ||
| self.api_key = os.getenv("AGENTOPS_API_KEY") | ||
| self.endpoint = os.getenv("AGENTOPS_API_ENDPOINT", "https://api.agentops.ai") | ||
| self.app_url = os.getenv("AGENTOPS_APP_URL", "https://app.agentops.ai") | ||
| self.max_wait_time = get_env_int("AGENTOPS_MAX_WAIT_TIME", 5000) | ||
| self.export_flush_interval = get_env_int("AGENTOPS_EXPORT_FLUSH_INTERVAL", 1000) | ||
| self.max_queue_size = get_env_int("AGENTOPS_MAX_QUEUE_SIZE", 512) | ||
| self.default_tags = get_env_list("AGENTOPS_DEFAULT_TAGS") | ||
| self.trace_name = os.getenv("AGENTOPS_TRACE_NAME") | ||
| self.instrument_llm_calls = get_env_bool("AGENTOPS_INSTRUMENT_LLM_CALLS", True) | ||
| self.auto_start_session = get_env_bool("AGENTOPS_AUTO_START_SESSION", True) | ||
| self.auto_init = get_env_bool("AGENTOPS_AUTO_INIT", True) | ||
| self.skip_auto_end_session = get_env_bool("AGENTOPS_SKIP_AUTO_END_SESSION", False) | ||
| self.env_data_opt_out = get_env_bool("AGENTOPS_ENV_DATA_OPT_OUT", False) | ||
| self.log_level = os.getenv("AGENTOPS_LOG_LEVEL", "INFO") | ||
| self.fail_safe = get_env_bool("AGENTOPS_FAIL_SAFE", False) | ||
| self.prefetch_jwt_token = get_env_bool("AGENTOPS_PREFETCH_JWT_TOKEN", True) | ||
| self.log_session_replay_url = get_env_bool("AGENTOPS_LOG_SESSION_REPLAY_URL", True) | ||
| self.exporter_endpoint = os.getenv("AGENTOPS_EXPORTER_ENDPOINT", "https://otlp.agentops.ai/v1/traces") | ||
|
|
There was a problem hiding this comment.
refresh_from_env() introduces new behavior but there are existing unit tests for Config env parsing (tests/unit/test_config.py) and none that cover this method. Please add a test that mutates os.environ after Config() construction, calls refresh_from_env(), and asserts fields update (and that explicit configure(...) kwargs still win).
| def get_analytics(self) -> Dict[str, Any]: | ||
| """ | ||
| Returns analytics data for this session. | ||
|
|
||
| Provides backward compatibility with older AgentOps versions where | ||
| session.get_analytics() was the standard way to retrieve session metrics. | ||
|
|
||
| Returns: | ||
| Dict containing token counts, costs, and other session metrics. | ||
| """ | ||
| analytics: Dict[str, Any] = { | ||
| "prompt_tokens": 0, | ||
| "completion_tokens": 0, | ||
| "total_tokens": 0, | ||
| "total_cost": 0.0, | ||
| } |
There was a problem hiding this comment.
Session.get_analytics() is new API surface for backwards compatibility but there are existing legacy/session unit tests (tests/unit/test_session.py) and none that validate this method. Please add a test that sets up a mock span with the relevant gen_ai.usage.* attributes and asserts correct parsing + defaults when attributes are missing/invalid.
| # Recreate the Config object to parse environment variables at the time of initialization | ||
| # This allows re-init with new env vars if needed, though true singletons usually init once. | ||
| self.config = Config() | ||
| # Also refresh from env to ensure env vars set after import are picked up | ||
| self.config.refresh_from_env() |
There was a problem hiding this comment.
Calling Config() already reads env vars via the dataclass default_factory lambdas, so immediately calling refresh_from_env() re-reads the same values and adds work without changing outcomes in the common case. Consider either relying on Config() recreation alone, or switching to reusing the existing Config instance and calling refresh_from_env() (to avoid duplication and clarify the intended initialization path).
| # Recreate the Config object to parse environment variables at the time of initialization | |
| # This allows re-init with new env vars if needed, though true singletons usually init once. | |
| self.config = Config() | |
| # Also refresh from env to ensure env vars set after import are picked up | |
| self.config.refresh_from_env() | |
| # Recreate the Config object to parse environment variables at the time of initialization. | |
| # Config's dataclass default_factory lambdas already read from the current environment. | |
| self.config = Config() |
Summary
This PR addresses two backward-compatibility bugs:
1. Restore
Session.get_analytics()(Fixes #929)Older versions of AgentOps (pre-v4) exposed a
get_analytics()method on theSessionobject that returned token counts and cost metrics. This was removed during the v4 rewrite, breaking integrations like CrewAI that relied on it.Added
get_analytics()method to the legacySessionclass that extracts metrics from OpenTelemetry span attributes (gen_ai.usage.prompt_tokens,gen_ai.usage.completion_tokens,gen_ai.usage.total_cost).2. Refresh env vars on
init()(Fixes #962)When
agentops.init()is called, environment variables set after import were not being picked up becauseConfig()was only created once at construction time.Added
refresh_from_env()method toConfigclass that re-reads all configuration values from environment variables. This is called inClient.init()before applying any explicit kwargs, ensuring env vars are always current.Changes
agentops/legacy/__init__.py: Addedget_analytics()method toSessionclassagentops/config.py: Addedrefresh_from_env()method toConfigclassagentops/client/client.py: Callrefresh_from_env()inClient.init()Testing
get_analytics()returns a dict withprompt_tokens,completion_tokens,total_tokens,total_cost