Skip to content

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

Merged
alexmojaki merged 19 commits intomainfrom
feat/url-from-eval
Mar 27, 2026
Merged

feat: add logfire.url_from_eval(report) method#1694
alexmojaki merged 19 commits intomainfrom
feat/url-from-eval

Conversation

@Kludex
Copy link
Copy Markdown
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
Copy Markdown

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

Deploying logfire-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: c3b864c
Status: ✅  Deploy successful!
Preview URL: https://b7e8b84d.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

This comment was marked as resolved.

@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
devin-ai-integration[bot]

This comment was marked as resolved.

- 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>
devin-ai-integration[bot]

This comment was marked as resolved.

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>
devin-ai-integration[bot]

This comment was marked as resolved.

@dmontagu
Copy link
Copy Markdown
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

Kludex and others added 3 commits March 25, 2026 11:57
Use a generation counter so that check_tokens threads from a previous
configure() call do not write a stale project_url. Also fix the
test_url_from_eval_waits_for_token_validation test to use tmp_path so
it does not read from a local credentials file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers the generation-check branches in check_tokens to satisfy
100% coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cubic-dev-ai[bot]

This comment was marked as resolved.

Use two tokens so the orphaned thread's second loop iteration hits the
generation-mismatch early return. Also assert that Event.wait and
Thread.join actually succeed within the timeout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Kludex and others added 2 commits March 25, 2026 14:00
Save _check_tokens_thread to a local variable before checking and
joining, so a concurrent configure() cannot set it to None between
the two reads.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

# Try loading credentials from a file.
# We do this before checking send_to_logfire so that project_url
# is available for url_from_eval even when not sending data.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

but should url_from_eval return something when not sending data?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No - addressed in bca78cf. Credential loading moved back inside the send_to_logfire block, so url_from_eval returns None when not sending data.

with suppress_instrumentation():
for token in token_list:
if self._config_generation != generation:
return
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

needs a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No longer applicable - the generation check was removed entirely in bca78cf.

raise
credentials = None
if credentials is not None:
self.project_url = self.project_url or credentials.project_url
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
self.project_url = self.project_url or credentials.project_url
self.project_url = credentials.project_url

when would this differ?

If project_url is not available (i.e. not sending data to Logfire),
url_from_eval returns None since the URL wouldn't work anyway.

This removes the early credential loading, background thread waiting,
and generation tracking that were added to support url_from_eval
when not sending data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

cubic-dev-ai[bot]

This comment was marked as resolved.

Kludex and others added 2 commits March 25, 2026 16:39
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the token comes from an env var without a creds file,
project_url was never set because credentials was None at that
point. Now check_tokens also sets project_url from the validated
credentials returned by the /v1/info endpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cubic-dev-ai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Kludex added 3 commits March 26, 2026 10:16
- Use ellipsis body style in logfire-api shim for url_from_eval
- Use ImportError instead of Exception in test import guard
The import chain can fail with AttributeError (not ImportError)
when pydantic is too old, since pydantic_evals transitively
imports pydantic_ai which uses pydantic.Tag.
cubic-dev-ai[bot]

This comment was marked as resolved.

@Kludex
Copy link
Copy Markdown
Member Author

Kludex commented Mar 26, 2026

Re: "P3: After printing a validated token summary, add the token to printed_tokens" - not an issue. Each token appears at most once in token_list, and printed_tokens already guards against double-printing: a token is added at line 1101 (eager print from creds file), and checked at line 1115 (background thread). There's no path where a token prints twice.

@Kludex Kludex requested a review from alexmojaki March 26, 2026 09:24
@Kludex
Copy link
Copy Markdown
Member Author

Kludex commented Mar 26, 2026

I think we are good now @alexmojaki

self.advanced = advanced

self.additional_span_processors = additional_span_processors
self.project_url: str | None = None
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Add _ prefix project_url.

devin-ai-integration[bot]

This comment was marked as resolved.

@Kludex
Copy link
Copy Markdown
Member Author

Kludex commented Mar 27, 2026

omg, I need to this manually

@Kludex Kludex force-pushed the feat/url-from-eval branch from 2f51fbc to d463c11 Compare March 27, 2026 08:58
@Kludex
Copy link
Copy Markdown
Member Author

Kludex commented Mar 27, 2026

@alexmojaki applied manually the leading underscore.

Copy link
Copy Markdown
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 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +896 to +910
def url_from_eval(self, report: EvaluationReport[Any, Any, Any]) -> str | None:
"""Generate a Logfire URL to view an evaluation report.

Args:
report: An evaluation report from `pydantic_evals`.

Returns:
The URL string, or `None` if the project URL or trace/span IDs are not available.
"""
project_url = self._config._project_url # type: ignore[reportPrivateUsage]
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.rstrip("/")}/evals/compare?experiment={trace_id}-{span_id}'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 _project_url availability depends on async token validation completing

When Logfire is configured with a token from an environment variable (no credentials file), _project_url will remain None until the background thread at logfire/_internal/config.py:1121 completes the HTTP request to /v1/info and sets it at line 1114. If url_from_eval is called before this thread completes, it will return None even though the project URL will eventually be available. This is documented behavior ('or None if the project URL ... are not available'), but users may find it surprising if they call url_from_eval immediately after configure(). A potential improvement would be to provide a way to wait for the background thread to complete, or to document the timing dependency more prominently.

Open in Devin Review

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

@alexmojaki alexmojaki merged commit 7ee31f6 into main Mar 27, 2026
22 checks passed
@alexmojaki alexmojaki deleted the feat/url-from-eval branch March 27, 2026 10:14
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