Skip to content

fix: restore get_analytics() on Session and refresh env vars on init#1311

Open
AmaTsumeAkira wants to merge 1 commit intoAgentOps-AI:mainfrom
AmaTsumeAkira:fix/issue-929-get-analytics
Open

fix: restore get_analytics() on Session and refresh env vars on init#1311
AmaTsumeAkira wants to merge 1 commit intoAgentOps-AI:mainfrom
AmaTsumeAkira:fix/issue-929-get-analytics

Conversation

@AmaTsumeAkira
Copy link

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 the Session object 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 legacy Session class 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 because Config() was only created once at construction time.

Added refresh_from_env() method to Config class that re-reads all configuration values from environment variables. This is called in Client.init() before applying any explicit kwargs, ensuring env vars are always current.

Changes

  • agentops/legacy/__init__.py: Added get_analytics() method to Session class
  • agentops/config.py: Added refresh_from_env() method to Config class
  • agentops/client/client.py: Call refresh_from_env() in Client.init()

Testing

  • Verified all modified files parse correctly (no syntax errors)
  • get_analytics() returns a dict with prompt_tokens, completion_tokens, total_tokens, total_cost
  • Gracefully handles missing/invalid span data by returning zeroed defaults

- 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)
Copilot AI review requested due to automatic review settings March 26, 2026 04:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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() during Client.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"]
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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"]

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +88
if hasattr(span, "_attributes"):
attrs = span._attributes
elif hasattr(span, "attributes"):
attrs = span.attributes
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
"""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().
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
environment variables and calling init() or configure().
environment variables and calling init().

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +163
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")

Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

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

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +78
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,
}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 148 to +152
# 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()
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

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

Suggested change
# 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()

Copilot uses AI. Check for mistakes.
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.

[Bug]: agentops init does not update the environment variables [Bug]: get_analytics got removed

2 participants