Skip to content

fix(routing): implement pure subdomain routing for LiteLLM, Langfuse, and Llama#342

Closed
sheepdestroyer wants to merge 1 commit into
masterfrom
feature/pure-subdomain-routing
Closed

fix(routing): implement pure subdomain routing for LiteLLM, Langfuse, and Llama#342
sheepdestroyer wants to merge 1 commit into
masterfrom
feature/pure-subdomain-routing

Conversation

@sheepdestroyer

@sheepdestroyer sheepdestroyer commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • Migrated default URL generation in router/main.py (resolve_external_urls) to use clean subdomains (litellm.*, langfuse.*, llama.*).
  • Updated .env.dev overlay and start-stack.sh placeholder replacement logic to support subdomain formatting (PROXY_BASE_URL and NEXTAUTH_URL).
  • Fixed urllib.parse import in start-stack.sh placeholder replacement.
  • Tested and verified against full dev stack (21/21 passed).

Summary by Sourcery

Adopt subdomain-based routing for LiteLLM, Langfuse, and Llama and align dev stack URL configuration with the new pattern.

Enhancements:

  • Update external URL resolution to generate clean service-specific subdomain URLs for LiteLLM, Langfuse, and Llama instead of path-based routes.
  • Adjust dev stack startup script to derive PROXY_BASE_URL and NEXTAUTH_URL using service subdomains, while honoring explicit environment overrides.
  • Fix URL parsing usage in the dev stack startup script when computing derived service URLs.

Build:

  • Refresh .env.dev overlay values to match the new subdomain-oriented routing and proxy configuration.

Summary by CodeRabbit

  • Bug Fixes
    • Updated development routing so Langfuse, LiteLLM, and Llama.cpp links use dedicated subdomains instead of path-based URLs.
    • Improved URL generation to support custom proxy and authentication URL overrides.
    • Added automatic development URL defaults based on the configured public domain.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@sourcery-ai

sourcery-ai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements pure subdomain-based routing for LiteLLM, Langfuse, and Llama across the router, stack startup script, and dev environment configuration, replacing path-based URLs and adding smarter env-derived URL construction.

Flow diagram for resolve_external_urls subdomain routing

flowchart TD
    A[Incoming Request] --> B[compute external_host and domain]
    B --> C{is_valid_external or is_valid_base}
    C -->|false| D[Local development fallback]
    C -->|true| E[select host_val from external_host or base_url.hostname]
    E --> F[remove dashboard. prefix]
    F --> G{host_base startswith litellm. or langfuse. or llama.}
    G -->|true| H[strip leading litellm. or langfuse. or llama.]
    G -->|false| I[keep host_base]
    H --> J[return langfuse.host_base, litellm.host_base/ui/, llama.host_base/]
    I --> J[return langfuse.host_base, litellm.host_base/ui/, llama.host_base/]
Loading

Flow diagram for start-stack.sh URL derivation using subdomains

flowchart TD
    A[Load PUBLIC_BASE_URL] --> B[parsed_pub = urllib.parse.urlparse]
    B --> C[scheme = parsed_pub.scheme or https]
    B --> D[host = parsed_pub.netloc or path head or vendeuvre.lan]
    C --> E{env has PROXY_BASE_URL}
    D --> E
    E -->|true| F[proxy_base_url = env PROXY_BASE_URL]
    E -->|false| G[proxy_base_url = scheme://litellm.host]
    C --> H{env has NEXTAUTH_URL}
    D --> H
    H -->|true| I[nextauth_url = env NEXTAUTH_URL]
    H -->|false| J[nextauth_url = scheme://langfuse.host]
    F --> K[replace PROXY_BASE_URL_PLACEHOLDER]
    G --> K
    I --> L[replace NEXTAUTH_URL_PLACEHOLDER]
    J --> L
Loading

File-Level Changes

Change Details Files
Switch external URL resolution from path-based routing to subdomain-based routing for Langfuse, LiteLLM, and Llama.
  • Unify handling of external and base host validation so both feed into subdomain resolution.
  • Derive a base host by stripping an optional dashboard. prefix from the host value.
  • Normalize litellm., langfuse., and llama. prefixes from the base host to avoid double subdomain segments.
  • Return external URLs using langfuse.<host>, litellm.<host>/ui/, and llama.<host>/ rather than /llm-routing/... paths.
router/main.py
Update stack startup script to derive proxy and auth URLs using subdomain-style hostnames with env overrides.
  • Parse PUBLIC_BASE_URL with urllib.parse.urlparse to extract scheme and host, falling back to sensible defaults.
  • Add conditional support for explicitly provided PROXY_BASE_URL and NEXTAUTH_URL environment variables.
  • Construct default PROXY_BASE_URL as scheme://litellm.<host> instead of appending /litellm path.
  • Construct default NEXTAUTH_URL as scheme://langfuse.<host> instead of appending /langfuse path.
  • Ensure the derived values are substituted into the YAML via the existing placeholder replacement mechanism.
start-stack.sh
Align development environment configuration with new subdomain routing behavior.
  • Adjust .env.dev values and placeholders to be compatible with subdomain-based PROXY_BASE_URL and NEXTAUTH_URL semantics.
  • Ensure dev stack routing domain and public URL settings reflect subdomain usage for Langfuse, LiteLLM, and Llama.
.env.dev

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've found 1 issue, and left some high level feedback:

  • In resolve_external_urls, the default fallback host of "vendeuvre.lan" and the hardcoded stripping of the dashboard. prefix may be too environment-specific; consider deriving these from configuration or ROUTING_DOMAIN to avoid surprising behavior in other deployments.
  • The new subdomain normalization logic in resolve_external_urls (re.sub(r"^(litellm|langfuse|llama)\.", "", host_base)) will strip those prefixes even if they are part of a legitimate hostname; consider tightening this to only operate on explicitly recognized routing domains to avoid accidental rewrites.
  • In start-stack.sh, the host derivation from PUBLIC_BASE_URL (parsed_pub.netloc or parsed_pub.path.split("/")[0]) could misbehave for atypical values (e.g., including paths or missing scheme); it may be safer to validate and fail fast or enforce a stricter expected format for PUBLIC_BASE_URL.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `resolve_external_urls`, the default fallback host of `"vendeuvre.lan"` and the hardcoded stripping of the `dashboard.` prefix may be too environment-specific; consider deriving these from configuration or `ROUTING_DOMAIN` to avoid surprising behavior in other deployments.
- The new subdomain normalization logic in `resolve_external_urls` (`re.sub(r"^(litellm|langfuse|llama)\.", "", host_base)`) will strip those prefixes even if they are part of a legitimate hostname; consider tightening this to only operate on explicitly recognized routing domains to avoid accidental rewrites.
- In `start-stack.sh`, the `host` derivation from `PUBLIC_BASE_URL` (`parsed_pub.netloc or parsed_pub.path.split("/")[0]`) could misbehave for atypical values (e.g., including paths or missing scheme); it may be safer to validate and fail fast or enforce a stricter expected format for `PUBLIC_BASE_URL`.

## Individual Comments

### Comment 1
<location path="router/main.py" line_range="3550" />
<code_context>
-    if is_valid_external:
-        # Centralized base URL path under subdomain/reverse proxy
+    if is_valid_external or is_valid_base:
+        host_val = external_host if is_valid_external else (request.base_url.hostname or "vendeuvre.lan")
+        host_base = host_val.replace("dashboard.", "")
+        if host_base.startswith("litellm.") or host_base.startswith("langfuse.") or host_base.startswith("llama."):
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Hardcoded "vendeuvre.lan" fallback may be surprising and environment-specific.

This hardcoded fallback in `host_val` risks misrouting in non-vendeuvre environments if `request.base_url.hostname` is unset or misconfigured. Consider deriving the fallback from configuration (e.g., `ROUTING_DOMAIN`), or failing fast/logging when neither `external_host` nor a valid base hostname is available, instead of silently defaulting to a specific LAN domain.

Suggested implementation:

```python
    if is_valid_external or is_valid_base:
        # Fallback to configured `domain` instead of an environment-specific hardcoded hostname
        host_val = external_host if is_valid_external else (request.base_url.hostname or domain)
        host_base = host_val.replace("dashboard.", "")

```

If your routing domain is configurable via a dedicated setting (e.g. `ROUTING_DOMAIN`), you may want to:
1. Initialize `domain` from that configuration, if it is not already.
2. Optionally add logging where `domain` is determined to surface misconfigurations (e.g. when `request.base_url.hostname` is unexpectedly `None` and the code falls back to `domain`).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread router/main.py
if is_valid_external:
# Centralized base URL path under subdomain/reverse proxy
if is_valid_external or is_valid_base:
host_val = external_host if is_valid_external else (request.base_url.hostname or "vendeuvre.lan")

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.

suggestion (bug_risk): Hardcoded "vendeuvre.lan" fallback may be surprising and environment-specific.

This hardcoded fallback in host_val risks misrouting in non-vendeuvre environments if request.base_url.hostname is unset or misconfigured. Consider deriving the fallback from configuration (e.g., ROUTING_DOMAIN), or failing fast/logging when neither external_host nor a valid base hostname is available, instead of silently defaulting to a specific LAN domain.

Suggested implementation:

    if is_valid_external or is_valid_base:
        # Fallback to configured `domain` instead of an environment-specific hardcoded hostname
        host_val = external_host if is_valid_external else (request.base_url.hostname or domain)
        host_base = host_val.replace("dashboard.", "")

If your routing domain is configurable via a dedicated setting (e.g. ROUTING_DOMAIN), you may want to:

  1. Initialize domain from that configuration, if it is not already.
  2. Optionally add logging where domain is determined to surface misconfigurations (e.g. when request.base_url.hostname is unexpectedly None and the code falls back to domain).

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Development URL configuration now uses dedicated LiteLLM and Langfuse subdomains. Stack rendering derives service URLs from the public host, and external URL resolution returns subdomain-based links for Langfuse, LiteLLM, and Llama.

Changes

Subdomain URL routing

Layer / File(s) Summary
Development URL configuration
.env.dev, start-stack.sh
Development variables and pod rendering now use root-based public URLs with configurable LiteLLM and Langfuse subdomain URLs.
External service URL resolution
router/main.py
Matching hosts now resolve Langfuse, LiteLLM, and Llama URLs through dedicated subdomains instead of /llm-routing/... paths.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: switching LiteLLM, Langfuse, and Llama routing to subdomains.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/pure-subdomain-routing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@router/main.py`:
- Around line 3554-3557: Update the URL construction in the surrounding function
to retain the explicit port from the configured/request netloc, rather than
using the hostname-only external_host base. Build the Langfuse, LiteLLM, and
Llama URLs from a shared host-and-port base so all three preserve ports such as
:8443 while retaining their existing paths.
- Around line 3549-3557: The resolver tests in test_resolve_external_urls.py
should be updated to match the new subdomain contract implemented by the
external URL resolver: for https://app.vendeuvre.lan, expect Langfuse at
https://langfuse.app.vendeuvre.lan, LiteLLM at
https://litellm.app.vendeuvre.lan/ui/, and Llama at
https://llama.app.vendeuvre.lan/. Replace the outdated /llm-routing/ path
expectations without changing resolver behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3f9597ca-0806-4b21-85c8-768da6015f1a

📥 Commits

Reviewing files that changed from the base of the PR and between 05b69f9 and 09c0f4c.

📒 Files selected for processing (3)
  • .env.dev
  • router/main.py
  • start-stack.sh

Comment thread router/main.py
Comment on lines +3549 to +3557
if is_valid_external or is_valid_base:
host_val = external_host if is_valid_external else (request.base_url.hostname or "vendeuvre.lan")
host_base = host_val.replace("dashboard.", "")
if host_base.startswith("litellm.") or host_base.startswith("langfuse.") or host_base.startswith("llama."):
host_base = re.sub(r"^(litellm|langfuse|llama)\.", "", host_base)
return (
f"{external_scheme}://{external_netloc}/llm-routing/langfuse",
f"{external_scheme}://{external_netloc}/llm-routing/litellm/ui",
f"{external_scheme}://{external_netloc}/llm-routing/llama/"
)
elif is_valid_base:
parsed_netloc = urlparse(f"{external_scheme}://{request.url.netloc}")
netloc = request.url.netloc if parsed_netloc.hostname else "localhost"
base = f"{external_scheme}://{netloc}"
return (
f"{base}/llm-routing/langfuse",
f"{base}/llm-routing/litellm/ui",
f"{base}/llm-routing/llama/"
f"{external_scheme}://langfuse.{host_base}",
f"{external_scheme}://litellm.{host_base}/ui/",
f"{external_scheme}://llama.{host_base}/"

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Update the resolver tests to the new subdomain contract.

router/tests/test_resolve_external_urls.py:41-51 still expects /llm-routing/... paths. With this implementation, the expected values for https://app.vendeuvre.lan are https://langfuse.app.vendeuvre.lan, https://litellm.app.vendeuvre.lan/ui/, and https://llama.app.vendeuvre.lan/.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router/main.py` around lines 3549 - 3557, The resolver tests in
test_resolve_external_urls.py should be updated to match the new subdomain
contract implemented by the external URL resolver: for
https://app.vendeuvre.lan, expect Langfuse at
https://langfuse.app.vendeuvre.lan, LiteLLM at
https://litellm.app.vendeuvre.lan/ui/, and Llama at
https://llama.app.vendeuvre.lan/. Replace the outdated /llm-routing/ path
expectations without changing resolver behavior.

Comment thread router/main.py
Comment on lines 3554 to +3557
return (
f"{external_scheme}://{external_netloc}/llm-routing/langfuse",
f"{external_scheme}://{external_netloc}/llm-routing/litellm/ui",
f"{external_scheme}://{external_netloc}/llm-routing/llama/"
)
elif is_valid_base:
parsed_netloc = urlparse(f"{external_scheme}://{request.url.netloc}")
netloc = request.url.netloc if parsed_netloc.hostname else "localhost"
base = f"{external_scheme}://{netloc}"
return (
f"{base}/llm-routing/langfuse",
f"{base}/llm-routing/litellm/ui",
f"{base}/llm-routing/llama/"
f"{external_scheme}://langfuse.{host_base}",
f"{external_scheme}://litellm.{host_base}/ui/",
f"{external_scheme}://llama.{host_base}/"

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve an explicit public port when constructing subdomain URLs.

external_host is hostname-only, so a base such as https://app.vendeuvre.lan:8443 produces links on default port 443. start-stack.sh preserves the port through parsed_pub.netloc, creating inconsistent rendered and runtime URLs.

Carry the explicit port from the configured/request netloc into all three returned URLs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router/main.py` around lines 3554 - 3557, Update the URL construction in the
surrounding function to retain the explicit port from the configured/request
netloc, rather than using the hostname-only external_host base. Build the
Langfuse, LiteLLM, and Llama URLs from a shared host-and-port base so all three
preserve ports such as :8443 while retaining their existing paths.

@sheepdestroyer

Copy link
Copy Markdown
Owner Author

Closing superseded by a consolidated replacement PR from f8a4a53.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant