Skip to content

feat: add logfire.url_from_eval(report) method#1694

Open
Kludex wants to merge 5 commits intomainfrom
feat/url-from-eval
Open

feat: add logfire.url_from_eval(report) method#1694
Kludex wants to merge 5 commits intomainfrom
feat/url-from-eval

Conversation

@Kludex
Copy link
Member

@Kludex Kludex commented Feb 9, 2026

Summary

  • Adds logfire.url_from_eval(report) method that generates a Logfire dashboard link to view a pydantic_evals.EvaluationReport
  • Stores project_url on LogfireConfig from credentials (file or token validation)
  • Constructs URL as {project_url}/evals/compare?experiment={trace_id}-{span_id}, returning None if any piece is missing
  • Adds pydantic-evals as a dev dependency for type checking

Test plan

  • make typecheck — 0 errors
  • make lint — all checks passed
  • uv run pytest tests/test_url_from_eval.py — 5 tests covering happy path and all None cases
  • uv run pytest tests/test_logfire_api.py — logfire-api shim tests pass
  • Pre-commit hooks pass

🤖 Generated with Claude Code

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 9, 2026

Deploying logfire-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 0b2ad3e
Status: ✅  Deploy successful!
Preview URL: https://ddd3cf9b.logfire-docs.pages.dev
Branch Preview URL: https://feat-url-from-eval.logfire-docs.pages.dev

View logs

@Kludex Kludex requested a review from Copilot February 9, 2026 09:22
@Kludex Kludex force-pushed the feat/url-from-eval branch 2 times, most recently from b86eb5c to 8cf0b55 Compare February 9, 2026 09:28
@Kludex Kludex changed the title feat: Add logfire.url_from_eval(report) method feat: add logfire.url_from_eval(report) method Feb 9, 2026
@Kludex Kludex requested a review from dmontagu February 9, 2026 09:29
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

This PR introduces a new logfire.url_from_eval(report) helper to generate a Logfire dashboard link for a pydantic_evals.EvaluationReport, and wires config support to retain the project’s dashboard URL derived from credentials/token validation.

Changes:

  • Added Logfire.url_from_eval() and exported it at the top-level logfire.url_from_eval.
  • Persisted project_url on LogfireConfig when loading credentials / validating tokens.
  • Added tests for URL generation and updated dev dependencies/lockfile to include pydantic-evals.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
logfire/_internal/main.py Adds Logfire.url_from_eval() URL construction logic.
logfire/_internal/config.py Stores project_url on config from creds/token validation.
logfire/__init__.py Re-exports url_from_eval and includes it in __all__.
logfire-api/logfire_api/__init__.py Exposes url_from_eval via the logfire-api runtime shim.
tests/test_url_from_eval.py Adds unit tests for URL generation and None cases.
tests/test_logfire_api.py Verifies url_from_eval is present in the logfire-api shim surface.
pyproject.toml Adds pydantic-evals to dev dependencies.
uv.lock Locks pydantic-evals and updates related dependency versions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +889 to +894
project_url = self._config.project_url
trace_id = report.trace_id
span_id = report.span_id
if not project_url or not trace_id or not span_id:
return None
return f'{project_url}/evals/compare?experiment={trace_id}-{span_id}'
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

project_url may come from credentials and could plausibly include a trailing / (e.g. if provided by a user or backend). Appending /{...} unconditionally can yield a double slash in the final URL. Consider normalizing with project_url = project_url.rstrip('/') before building the path (and ideally use urllib.parse.urlencode for the query string).

Copilot uses AI. Check for mistakes.
@Kludex Kludex force-pushed the feat/url-from-eval branch 2 times, most recently from edb283a to dd8d80b Compare February 9, 2026 09:34
…rd links for eval reports

Users running pydantic-evals evaluations can now easily get a Logfire dashboard link
to view their evaluation report by calling `logfire.url_from_eval(report)`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Kludex Kludex force-pushed the feat/url-from-eval branch from dd8d80b to d654bed Compare February 9, 2026 09:38
@Kludex Kludex requested a review from alexmojaki February 9, 2026 09:46
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

- Strip trailing slash from project_url before building URL
- Load project_url from credentials file regardless of send_to_logfire
- Add test for trailing slash handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Kludex and others added 3 commits February 9, 2026 16:14
…_eval

When the token is provided via env var without a credentials file,
project_url is only populated in a background thread. url_from_eval
now joins that thread first to ensure the URL is available.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uses requests_mock to exercise the full path: configure with token
(no creds file) -> background thread validates token -> url_from_eval
waits for thread -> returns correct URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

View 8 additional findings in Devin Review.

Open in Devin Review

Comment on lines +699 to +700
self.project_url: str | None = None
self._check_tokens_thread: Thread | None = None
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 Stale project_url race when configure() is called multiple times due to orphaned background thread

When configure() is called a second time, _load_configuration() resets self._check_tokens_thread = None and self.project_url = None without joining the previous background thread. The orphaned thread from the first configure() can still write a stale project_url after the reset, and because self.project_url or ... is used, the new thread will then see it as already set and won't overwrite it.

Root cause and scenario
  1. logfire.configure(token='token-A')_load_configuration resets state, initialize() starts thread T1 which calls self._initialize_credentials_from_token('token-A') (an HTTP request)
  2. Before T1 finishes, logfire.configure(token='token-B') is called → _load_configuration() at logfire/_internal/config.py:699-700 resets self.project_url = None and self._check_tokens_thread = None (losing the T1 reference), then initialize() starts thread T2 for token-B
  3. T1 finishes its HTTP request and executes self.project_url = self.project_url or validated_credentials.project_url at logfire/_internal/config.py:1004, setting project_url to project-A's URL
  4. T2 finishes and runs the same line, but since self.project_url is already set (by T1), the or short-circuits and project-B's URL is never stored
  5. url_from_eval() at logfire/_internal/main.py:891 calls wait_for_token_validation() which only joins T2 (T1's reference was lost), reads self.project_url, and returns a URL pointing to the wrong project

Impact: url_from_eval could return a URL for a previous project configuration's dashboard instead of the current one. This is limited to the uncommon case of calling configure() multiple times with different tokens where the first token validation thread hasn't finished.

Prompt for agents
In _load_configuration (logfire/_internal/config.py around line 699), before resetting self._check_tokens_thread = None, join the old thread (if it exists) to ensure it has finished writing. This prevents the orphaned thread from writing a stale project_url after the reset. For example:

    self.additional_span_processors = additional_span_processors
    self.project_url: str | None = None
    if hasattr(self, '_check_tokens_thread') and self._check_tokens_thread is not None:
        self._check_tokens_thread.join(timeout=0)  # non-blocking, just try
    self._check_tokens_thread: Thread | None = None

Alternatively, a more robust approach is to use a generation counter: increment a counter in _load_configuration, capture it in the check_tokens closure, and only write self.project_url if the generation still matches. This avoids blocking configure() on an HTTP request.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@dmontagu
Copy link
Contributor

I feel like we need a place to put functions that are specific to our web platform, ideally grouped a bit more by function. There are going to be variables soon, then datasets, and more to come. I feel like we should be a bit thoughtful about where the APIs go rather than just haphazardly adding methods to the logfire instance. But 🤷 if it unblocks stuff it's probably worth biasing toward action

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.

4 participants