Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions agentops/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ def init(self, **kwargs: Any) -> None: # Return type updated to None
# 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()
Comment on lines 148 to +152
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.
self.configure(**kwargs)

# Only treat as re-initialization if a different non-None API key is explicitly provided
Expand Down
26 changes: 26 additions & 0 deletions agentops/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,32 @@ class Config:
default_factory=lambda: None, metadata={"description": "Custom span processor for OpenTelemetry trace data"}
)

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

Comment on lines +138 to +163
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.
def configure(
self,
api_key: Optional[str] = None,
Expand Down
40 changes: 40 additions & 0 deletions agentops/legacy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,46 @@ def end_session(self, **kwargs: Any):
"""Ends the session for CrewAI >= 0.105.0 compatibility. Calls the global legacy end_session."""
end_session(session_or_status=self, **kwargs)

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,
}
Comment on lines +63 to +78
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.

if not self.trace_context or not self.trace_context.span:
return analytics

span = self.trace_context.span
try:
if hasattr(span, "_attributes"):
attrs = span._attributes
elif hasattr(span, "attributes"):
attrs = span.attributes
Comment on lines +85 to +88
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.
else:
return analytics

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.
cost_val = attrs.get("gen_ai.usage.total_cost", 0) or 0
analytics["total_cost"] = float(cost_val) if cost_val else 0.0
except (TypeError, ValueError, AttributeError):
pass

return analytics


@deprecated("Use agentops.start_trace() instead.")
def start_session(
Expand Down
Loading