Skip to content

feat: add Requesty as a model provider#14020

Open
Thibaultjaigu wants to merge 1 commit into
langflow-ai:mainfrom
Thibaultjaigu:feat/requesty-provider
Open

feat: add Requesty as a model provider#14020
Thibaultjaigu wants to merge 1 commit into
langflow-ai:mainfrom
Thibaultjaigu:feat/requesty-provider

Conversation

@Thibaultjaigu

@Thibaultjaigu Thibaultjaigu commented Jul 12, 2026

Copy link
Copy Markdown

Summary

Adds Requesty — an OpenAI-compatible LLM router — as a first-class model provider, mirroring the existing OpenRouter integration as closely as possible. Requesty exposes 400+ models through one OpenAI-compatible endpoint, addressed as provider/model (the same naming scheme as OpenRouter).

Changes

Backend (lfx)

  • components/requesty/ — new component mirroring the OpenRouter component (base URL https://router.requesty.ai/v1, REQUESTY_API_KEY, provider/model naming, optional HTTP-Referer / X-Title attribution headers)
  • base/models/requesty_constants.py — seed catalog shown before a key is configured
  • base/models/model_utils.pyfetch_live_requesty_models for the live /v1/models catalog. Requesty's payload differs from OpenRouter's: capabilities are flat booleans (supports_tool_calling / supports_reasoning) rather than a supported_parameters array, and the context field is context_window (not context_length) — the fetcher maps the real fields.
  • base/models/model_metadata.pyMODEL_PROVIDER_METADATA["Requesty"] + LIVE_MODEL_PROVIDERS
  • base/models/unified_models/credentials.py — key validation via a 1-token /v1/chat/completions probe. Requesty's /v1/models is a public catalog (returns 200 for any bearer), so it can't detect an invalid key; the chat endpoint returns 401/403 for a bad key.
  • base/models/unified_models/instantiation.py — Requesty folded into the shared OpenAI-compatible get_llm path alongside OpenRouter
  • base/models/unified_models/provider_queries.py, components/__init__.py, services/settings/constants.py — registration + REQUESTY_* env-var auto-import

Frontend

  • icons/Requesty/ icon registered in the eager/lazy icon imports (mirrors the OpenRouter icon module). This is a simple monogram placeholder — happy to swap in an official brand asset if you prefer.

Testing

  • pytest tests/unit/base/models/test_requesty_provider.py — 21 new unit tests passing (metadata registration, live-fetch capability/default/degradation paths, credential validation success/401/403/network, and get_llm base-URL + attribution-header wiring), mirroring test_openrouter_provider.py
  • ruff check clean on all touched Python files
  • Verified a live chat completion through the component's own build_model() path against https://router.requesty.ai/v1 (model openai/gpt-4o-mini)

The frontend icon files mirror the existing OpenRouter icon module exactly; the backend was fully installed and tested (frontend deps were not installed in my environment, so the icon was verified by structural parity rather than a frontend build).

I work at Requesty. This mirrors the existing OpenRouter provider as closely as possible. Happy to adjust or close it if it's not a fit.

Summary by CodeRabbit

  • New Features
    • Added Requesty as a supported model provider.
    • Added a Requesty component with model selection, live model refresh, API key configuration, and generation settings.
    • Added Requesty model discovery, capability metadata, and OpenAI-compatible model integration.
    • Added Requesty branding and icon support throughout the interface.
    • Added support for Requesty credentials and optional site attribution settings.
  • Tests
    • Added coverage for model discovery, credential validation, configuration, and error handling.

Add Requesty (https://requesty.ai), an OpenAI-compatible LLM router, as a
first-class model provider by mirroring the existing OpenRouter integration.

Backend (lfx):
- new Requesty component under components/requesty/ mirroring the OpenRouter
  component (base URL https://router.requesty.ai/v1, REQUESTY_API_KEY,
  provider/model naming, HTTP-Referer/X-Title attribution headers)
- requesty_constants.py seed catalog + live /v1/models fetch in model_utils
  (maps Requesty's flat supports_* booleans and context_window field)
- MODEL_PROVIDER_METADATA + LIVE_MODEL_PROVIDERS registration
- credential validation via /v1/chat/completions (Requesty /v1/models is a
  public catalog, so it cannot detect an invalid key)
- unified-models instantiation folds Requesty into the OpenAI-compatible path
- REQUESTY_* env vars auto-imported as global variables

Frontend:
- Requesty icon registered in eager/lazy icon imports

Tests: 21 unit tests mirroring the OpenRouter provider tests.
Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds Requesty as a supported provider with model metadata, live model discovery, credential validation, OpenAI-compatible instantiation, a dedicated component, frontend icon registration, and unit tests.

Changes

Requesty provider integration

Layer / File(s) Summary
Provider metadata and static catalog
src/lfx/src/lfx/base/models/model_metadata.py, src/lfx/src/lfx/base/models/requesty_constants.py, src/lfx/src/lfx/base/models/unified_models/provider_queries.py, src/lfx/src/lfx/services/settings/constants.py
Registers Requesty metadata, seed models, live-provider eligibility, static catalog inclusion, and environment variables.
Live model retrieval
src/lfx/src/lfx/base/models/model_utils.py, src/lfx/tests/unit/base/models/test_requesty_provider.py
Fetches and normalizes Requesty models, maps capabilities and defaults, dispatches provider retrieval, and tests error handling.
Requesty component and lazy loading
src/lfx/src/lfx/components/__init__.py, src/lfx/src/lfx/components/requesty/*
Adds lazy component exposure, Requesty inputs, model refresh behavior, and ChatOpenAI construction.
Credential validation and LLM wiring
src/lfx/src/lfx/base/models/unified_models/credentials.py, src/lfx/src/lfx/base/models/unified_models/instantiation.py, src/lfx/tests/unit/base/models/test_requesty_provider.py
Adds API-key probing and Requesty base URL, model, and attribution-header configuration with tests.
Frontend icon registration
src/frontend/src/icons/Requesty/*, src/frontend/src/icons/eagerIconImports.ts, src/frontend/src/icons/lazyIconImports.ts
Adds the Requesty SVG icon and eager/lazy icon mappings.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant RequestyComponent
  participant RequestyAPI
  participant ChatOpenAI
  User->>RequestyComponent: configure API key and model
  RequestyComponent->>RequestyAPI: fetch available models
  RequestyAPI-->>RequestyComponent: return model metadata
  RequestyComponent->>ChatOpenAI: build with Requesty base URL and headers
  ChatOpenAI-->>User: provide configured language model
Loading

Possibly related PRs

Suggested labels: enhancement, test

Suggested reviewers: Cristhianzl, erichare, mendonk


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
Test Coverage For New Implementations ❌ Error Backend Requesty tests are thorough, but the new Requesty component/icon changes have no corresponding tests; only test_requesty_provider.py was added. Add tests for the new RequestyComponent and frontend icon registration (e.g. *.test.tsx), covering fetch_models/update_build_config/build_model and icon export wiring.
Docstring Coverage ⚠️ Warning Docstring coverage is 47.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Quality And Coverage ⚠️ Warning Provider tests are solid, but the new RequestyComponent and frontend icon have no tests; fetch_models/build_model/update_build_config and icon forwarding are unverified. Add unit tests for RequestyComponent behavior (fetch_models, update_build_config, build_model) and a frontend test/snapshot for icon registration/props.
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding Requesty as a model provider.
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.
Test File Naming And Structure ✅ Passed New backend test file is a properly named pytest module with descriptive unit tests, clear sectioning, helper setup, and positive/negative coverage; no misnamed integration tests found.
Excessive Mock Usage Warning ✅ Passed Mocks are confined to external boundaries (httpx/requests/env/model class); usage is comparable to the existing OpenRouter tests and not excessive.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 12, 2026

@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: 6

🤖 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 `@src/frontend/src/icons/Requesty/index.tsx`:
- Around line 5-9: Update the `RequestyIcon` forwardRef props type to
`React.SVGProps<SVGSVGElement> & { isDark?: boolean }`, preserving the existing
`ref` forwarding and prop spread so standard SVG attributes and the optional
`isDark` prop are exposed.

In `@src/frontend/src/icons/Requesty/RequestyIcon.jsx`:
- Around line 1-17: Update SvgRequesty to use React.forwardRef, accept the
forwarded ref, and attach it to the underlying svg element. Destructure isDark
from the component props before spreading the remaining props so it is not
emitted on the DOM element, while preserving all other SVG attributes and
exports.

In `@src/lfx/src/lfx/base/models/model_utils.py`:
- Around line 670-720: Update the Requesty response handling before
`.get("data", [])` so the decoded JSON must be a dictionary; otherwise log the
malformed payload and return []. Validate each model ID is a string before
inserting into `by_id`, ensuring `sorted_ids` cannot compare incompatible types
while preserving the existing empty-catalog fallback.

In `@src/lfx/src/lfx/base/models/unified_models/credentials.py`:
- Around line 517-544: The Requesty authentication probe in the credential
validation flow hardcodes openai/gpt-4o-mini instead of using the configured
validation_model. Update the requests.post payload to use validation_model, or
resolve and select an allowed model from the authenticated catalog before
probing, so model-specific 4xx responses do not incorrectly appear as
connectivity failures.

In `@src/lfx/src/lfx/components/requesty/requesty.py`:
- Around line 80-85: Update the model validation in build_model to reject the
"Failed to load models" sentinel in addition to missing values and "Loading...".
Preserve the existing "Please select a model" ValueError message and leave the
API-key validation unchanged.
- Around line 46-65: Update fetch_models to catch json.JSONDecodeError alongside
the existing HTTP exceptions around response.json(), ensuring malformed
successful responses are logged and return [] without escaping the handler.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e1632382-a341-4521-a99a-e8a24a46ee47

📥 Commits

Reviewing files that changed from the base of the PR and between def832f and 34a9d28.

📒 Files selected for processing (15)
  • src/frontend/src/icons/Requesty/RequestyIcon.jsx
  • src/frontend/src/icons/Requesty/index.tsx
  • src/frontend/src/icons/eagerIconImports.ts
  • src/frontend/src/icons/lazyIconImports.ts
  • src/lfx/src/lfx/base/models/model_metadata.py
  • src/lfx/src/lfx/base/models/model_utils.py
  • src/lfx/src/lfx/base/models/requesty_constants.py
  • src/lfx/src/lfx/base/models/unified_models/credentials.py
  • src/lfx/src/lfx/base/models/unified_models/instantiation.py
  • src/lfx/src/lfx/base/models/unified_models/provider_queries.py
  • src/lfx/src/lfx/components/__init__.py
  • src/lfx/src/lfx/components/requesty/__init__.py
  • src/lfx/src/lfx/components/requesty/requesty.py
  • src/lfx/src/lfx/services/settings/constants.py
  • src/lfx/tests/unit/base/models/test_requesty_provider.py

Comment on lines +5 to +9
export const RequestyIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {
return <SvgRequesty ref={ref} {...props} />;

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## File listing\n'
git ls-files 'src/frontend/src/icons/**' | sed -n '1,120p'

printf '\n## Outline of target file\n'
ast-grep outline src/frontend/src/icons/Requesty/index.tsx --view expanded || true

printf '\n## Relevant file contents\n'
cat -n src/frontend/src/icons/Requesty/index.tsx | sed -n '1,120p'

printf '\n## Search for icon prop patterns\n'
rg -n "PropsWithChildren|SVGProps<SVGSVGElement>|isDark|forwardRef<" src/frontend/src/icons -g '*.tsx' -g '*.ts' || true

Repository: langflow-ai/langflow

Length of output: 36489


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Requesty icon files\n'
git ls-files 'src/frontend/src/icons/Requesty/**' | sed -n '1,120p'

printf '\n## Requesty SVG component\n'
cat -n src/frontend/src/icons/Requesty/RequestyIcon.tsx | sed -n '1,200p' || true

printf '\n## Search for RequestyIcon usage and isDark patterns\n'
rg -n "RequestyIcon|SvgRequesty|isDark" src/frontend/src -g '*.ts' -g '*.tsx' | sed -n '1,200p'

printf '\n## Similar icon component with explicit SVG props\n'
cat -n src/frontend/src/icons/AIML/index.tsx | sed -n '1,120p' || true

Repository: langflow-ai/langflow

Length of output: 4665


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Generic icon component typing\n'
cat -n src/frontend/src/components/common/genericIconComponent/index.tsx | sed -n '1,240p'

printf '\n## Requesty icon implementation\n'
cat -n src/frontend/src/icons/Requesty/RequestyIcon.jsx | sed -n '1,240p'

printf '\n## Lazy icon import entry\n'
sed -n '395,410p' src/frontend/src/icons/lazyIconImports.ts

printf '\n## Eager icon import entry\n'
sed -n '75,92p' src/frontend/src/icons/eagerIconImports.ts

Repository: langflow-ai/langflow

Length of output: 9538


Expose the SVG prop contract on RequestyIcon. React.PropsWithChildren<{}> drops standard SVG attrs and the isDark prop that this wrapper forwards at runtime. Typing it as React.SVGProps<SVGSVGElement> & { isDark?: boolean } keeps the component aligned with its actual usage.

🤖 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 `@src/frontend/src/icons/Requesty/index.tsx` around lines 5 - 9, Update the
`RequestyIcon` forwardRef props type to `React.SVGProps<SVGSVGElement> & {
isDark?: boolean }`, preserving the existing `ref` forwarding and prop spread so
standard SVG attributes and the optional `isDark` prop are exposed.

Source: Coding guidelines

Comment on lines +1 to +17
const SvgRequesty = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<rect width="24" height="24" rx="5" fill="#6366F1" />
<path
d="M7.4 17.5V6.5h4.35c1.28 0 2.28.33 3 .98.72.65 1.08 1.55 1.08 2.7 0 .82-.2 1.52-.6 2.1-.4.57-.97.98-1.7 1.24l2.67 3.98h-2.62l-2.32-3.63H9.6v3.63H7.4Zm2.2-5.42h1.98c.6 0 1.06-.15 1.38-.45.33-.3.5-.72.5-1.25 0-.53-.17-.94-.5-1.24-.32-.3-.78-.45-1.38-.45H9.6v3.39Z"
fill="#fff"
/>
</svg>
);
export default SvgRequesty;

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'forwardRef|isDark' src/frontend/src/icons/Requesty

Repository: langflow-ai/langflow

Length of output: 322


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## src/frontend/src/icons/Requesty/index.tsx\n'
cat -n src/frontend/src/icons/Requesty/index.tsx

printf '\n## src/frontend/src/icons/Requesty/RequestyIcon.jsx\n'
cat -n src/frontend/src/icons/Requesty/RequestyIcon.jsx

printf '\n## Nearby icon pattern samples\n'
fd -a 'index.tsx' src/frontend/src/icons | head -n 20

Repository: langflow-ai/langflow

Length of output: 2523


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## RequestyIcon usages\n'
rg -n 'RequestyIcon|<RequestyIcon|RequestyIcon\(' src/frontend/src | head -n 80

printf '\n## isDark in icon set\n'
rg -n '\bisDark\b' src/frontend/src/icons src/frontend/src | head -n 120

printf '\n## Requesty sibling patterns\n'
for f in src/frontend/src/icons/*/RequestyIcon.jsx; do
  [ -f "$f" ] && printf '\n### %s\n' "$f" && sed -n '1,40p' "$f"
done | head -n 200

Repository: langflow-ai/langflow

Length of output: 10440


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## genericIconComponent\n'
sed -n '1,220p' src/frontend/src/components/common/genericIconComponent/index.tsx

printf '\n## sibling icon index files\n'
for f in src/frontend/src/icons/Anthropic/index.tsx src/frontend/src/icons/IBM/db2/DB2.jsx src/frontend/src/icons/CometAPI/index.tsx; do
  [ -f "$f" ] && printf '\n### %s\n' "$f" && cat -n "$f"
done

Repository: langflow-ai/langflow

Length of output: 7531


Wrap SvgRequesty with forwardRef and omit isDark from the SVG spread.
In src/frontend/src/icons/Requesty/RequestyIcon.jsx, the ref from src/frontend/src/icons/Requesty/index.tsx stops at the function component, so it never reaches the <svg>. isDark is also forwarded onto the DOM element unless it’s destructured first.

🤖 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 `@src/frontend/src/icons/Requesty/RequestyIcon.jsx` around lines 1 - 17, Update
SvgRequesty to use React.forwardRef, accept the forwarded ref, and attach it to
the underlying svg element. Destructure isDark from the component props before
spreading the remaining props so it is not emitted on the DOM element, while
preserving all other SVG attributes and exports.

Source: Coding guidelines

Comment on lines +670 to +720
try:
response = httpx.get(url, headers=headers, timeout=REQUESTY_FETCH_TIMEOUT)
response.raise_for_status()
raw_models = response.json().get("data", [])
except (httpx.RequestError, httpx.HTTPStatusError) as e:
# Surface as a warning (not debug) so a user who saved a key and sees
# an empty model catalog has a server-side breadcrumb.
status_code = getattr(getattr(e, "response", None), "status_code", None)
logger.warning("Could not fetch live Requesty models from %s (status=%s): %s", url, status_code, e)
return []
except (ValueError, TypeError) as e:
# 200 with malformed JSON or an unexpected payload shape — degrade to
# an empty catalog rather than crashing the caller.
logger.warning("Malformed Requesty /models response from %s: %s", url, e)
return []

if not isinstance(raw_models, list):
logger.warning("Unexpected Requesty /models payload (data is %s): %r", type(raw_models).__name__, raw_models)
return []

by_id: dict[str, dict] = {}
for raw in raw_models:
if not isinstance(raw, dict):
continue
mid = raw.get("id")
if not mid:
continue
created_raw = raw.get("created")
# Requesty exposes ``created`` as a Unix epoch (seconds). Defensive
# int-coercion handles the occasional string or null in the payload
# without bringing the whole fetch down.
try:
created = int(created_raw) if created_raw is not None else 0
except (TypeError, ValueError):
created = 0
by_id[mid] = {
# Requesty reports capabilities as flat booleans, unlike
# OpenRouter's ``supported_parameters`` array. Coerce to bool so a
# missing/null field degrades to False rather than surfacing a
# truthy string.
"tool_calling": bool(raw.get("supports_tool_calling")),
"reasoning": bool(raw.get("supports_reasoning")),
"created": max(created, 0),
}
if not by_id:
return []

sorted_ids = sorted(by_id)
seed_ids = {m["name"] for m in REQUESTY_MODELS_DETAILED}
intersected_defaults = seed_ids & by_id.keys()
default_set = intersected_defaults or set(sorted_ids[:MIN_DEFAULT_MODELS])

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate the complete response shape before indexing and sorting.

Line 673 raises AttributeError when valid JSON is a list or null, while non-string IDs can fail dictionary insertion or sorted(by_id). These cases escape the promised [] degradation and can break model-option retrieval.

Proposed fix
-        raw_models = response.json().get("data", [])
+        payload = response.json()
+        if not isinstance(payload, dict):
+            logger.warning(
+                "Unexpected Requesty /models payload type: %s",
+                type(payload).__name__,
+            )
+            return []
+        raw_models = payload.get("data", [])
...
         mid = raw.get("id")
-        if not mid:
+        if not isinstance(mid, str) or not mid:
             continue
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
response = httpx.get(url, headers=headers, timeout=REQUESTY_FETCH_TIMEOUT)
response.raise_for_status()
raw_models = response.json().get("data", [])
except (httpx.RequestError, httpx.HTTPStatusError) as e:
# Surface as a warning (not debug) so a user who saved a key and sees
# an empty model catalog has a server-side breadcrumb.
status_code = getattr(getattr(e, "response", None), "status_code", None)
logger.warning("Could not fetch live Requesty models from %s (status=%s): %s", url, status_code, e)
return []
except (ValueError, TypeError) as e:
# 200 with malformed JSON or an unexpected payload shape — degrade to
# an empty catalog rather than crashing the caller.
logger.warning("Malformed Requesty /models response from %s: %s", url, e)
return []
if not isinstance(raw_models, list):
logger.warning("Unexpected Requesty /models payload (data is %s): %r", type(raw_models).__name__, raw_models)
return []
by_id: dict[str, dict] = {}
for raw in raw_models:
if not isinstance(raw, dict):
continue
mid = raw.get("id")
if not mid:
continue
created_raw = raw.get("created")
# Requesty exposes ``created`` as a Unix epoch (seconds). Defensive
# int-coercion handles the occasional string or null in the payload
# without bringing the whole fetch down.
try:
created = int(created_raw) if created_raw is not None else 0
except (TypeError, ValueError):
created = 0
by_id[mid] = {
# Requesty reports capabilities as flat booleans, unlike
# OpenRouter's ``supported_parameters`` array. Coerce to bool so a
# missing/null field degrades to False rather than surfacing a
# truthy string.
"tool_calling": bool(raw.get("supports_tool_calling")),
"reasoning": bool(raw.get("supports_reasoning")),
"created": max(created, 0),
}
if not by_id:
return []
sorted_ids = sorted(by_id)
seed_ids = {m["name"] for m in REQUESTY_MODELS_DETAILED}
intersected_defaults = seed_ids & by_id.keys()
default_set = intersected_defaults or set(sorted_ids[:MIN_DEFAULT_MODELS])
try:
response = httpx.get(url, headers=headers, timeout=REQUESTY_FETCH_TIMEOUT)
response.raise_for_status()
payload = response.json()
if not isinstance(payload, dict):
logger.warning(
"Unexpected Requesty /models payload type: %s",
type(payload).__name__,
)
return []
raw_models = payload.get("data", [])
except (httpx.RequestError, httpx.HTTPStatusError) as e:
# Surface as a warning (not debug) so a user who saved a key and sees
# an empty model catalog has a server-side breadcrumb.
status_code = getattr(getattr(e, "response", None), "status_code", None)
logger.warning("Could not fetch live Requesty models from %s (status=%s): %s", url, status_code, e)
return []
except (ValueError, TypeError) as e:
# 200 with malformed JSON or an unexpected payload shape — degrade to
# an empty catalog rather than crashing the caller.
logger.warning("Malformed Requesty /models response from %s: %s", url, e)
return []
if not isinstance(raw_models, list):
logger.warning("Unexpected Requesty /models payload (data is %s): %r", type(raw_models).__name__, raw_models)
return []
by_id: dict[str, dict] = {}
for raw in raw_models:
if not isinstance(raw, dict):
continue
mid = raw.get("id")
if not isinstance(mid, str) or not mid:
continue
created_raw = raw.get("created")
# Requesty exposes ``created`` as a Unix epoch (seconds). Defensive
# int-coercion handles the occasional string or null in the payload
# without bringing the whole fetch down.
try:
created = int(created_raw) if created_raw is not None else 0
except (TypeError, ValueError):
created = 0
by_id[mid] = {
# Requesty reports capabilities as flat booleans, unlike
# OpenRouter's ``supported_parameters`` array. Coerce to bool so a
# missing/null field degrades to False rather than surfacing a
# truthy string.
"tool_calling": bool(raw.get("supports_tool_calling")),
"reasoning": bool(raw.get("supports_reasoning")),
"created": max(created, 0),
}
if not by_id:
return []
sorted_ids = sorted(by_id)
seed_ids = {m["name"] for m in REQUESTY_MODELS_DETAILED}
intersected_defaults = seed_ids & by_id.keys()
default_set = intersected_defaults or set(sorted_ids[:MIN_DEFAULT_MODELS])
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 670-670: Request-controlled URL passed to httpx; validate against an allowlist to prevent SSRF.
Context: httpx.get(url, headers=headers, timeout=REQUESTY_FETCH_TIMEOUT)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(avoid-ssrf)

🤖 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 `@src/lfx/src/lfx/base/models/model_utils.py` around lines 670 - 720, Update
the Requesty response handling before `.get("data", [])` so the decoded JSON
must be a dictionary; otherwise log the malformed payload and return [].
Validate each model ID is a string before inserting into `by_id`, ensuring
`sorted_ids` cannot compare incompatible types while preserving the existing
empty-catalog fallback.

Comment on lines +517 to +544
# Requesty's ``/v1/models`` is a public catalog (returns 200 for any
# bearer, including missing/invalid) and there is no dedicated
# auth-check endpoint, so validate against ``/v1/chat/completions``
# with a 1-token request. Invalid keys return 401/403; a valid key
# returns 200. The tiny completion keeps the round-trip cheap.
try:
response = requests.post(
"https://router.requesty.ai/v1/chat/completions",
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
json={
"model": "openai/gpt-4o-mini",
"messages": [{"role": "user", "content": "ping"}],
"max_tokens": 1,
},
timeout=10,
)
if response.status_code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN):
msg = "Invalid Requesty API key"
logger.error(msg)
raise ValueError(msg)
response.raise_for_status()
except ValueError:
raise
except requests.RequestException as e:
# Network/timeout/5xx during validation: surface as ValueError so
# the variable API returns a user-facing 400 instead of an
# unhandled 500 (api/v1/variable.py only catches ValueError).
msg = f"Could not reach Requesty to validate the API key: {e}"

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and surrounding symbols first.
ast-grep outline src/lfx/src/lfx/base/models/unified_models/credentials.py --view expanded

# Read the relevant section with line numbers.
sed -n '480,590p' src/lfx/src/lfx/base/models/unified_models/credentials.py | cat -n

# Find any other Requesty-specific validation or model-selection logic.
rg -n "Requesty|router.requesty.ai|chat/completions|/v1/models|model_name|openai/gpt-4o-mini" src/lfx/src/lfx/base/models/unified_models -S

Repository: langflow-ai/langflow

Length of output: 16621


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the full validation function and the nearby caller context.
sed -n '397,590p' src/lfx/src/lfx/base/models/unified_models/credentials.py | cat -n

echo
echo '--- callers / usage ---'
rg -n "validate_model_provider_key\(" src/lfx/src/lfx/base/models -S

echo
echo '--- Requesty docs / references in repo ---'
rg -n "Requesty|router.requesty.ai|openai/gpt-4o-mini|auth/key|/v1/models" -S .

Repository: langflow-ai/langflow

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the Requesty component to see how it treats /v1/models.
sed -n '1,180p' src/lfx/src/lfx/components/requesty/requesty.py | cat -n

echo
echo '--- nearby tests/docs mentioning Requesty models ---'
rg -n "Requesty|router.requesty.ai/v1/models|Authorization" src -S | head -n 80

Repository: langflow-ai/langflow

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the Requesty live-model discovery helper and its assumptions.
sed -n '630,760p' src/lfx/src/lfx/base/models/model_utils.py | cat -n

echo
echo '--- Requesty constants ---'
sed -n '1,120p' src/lfx/src/lfx/base/models/requesty_constants.py | cat -n

echo
echo '--- any tests for Requesty model discovery / validation ---'
rg -n "fetch_live_requesty_models|Requesty.*models|gpt-4o-mini|validate_model_provider_key|router.requesty.ai/v1/chat/completions" src/backend/tests src/lfx/tests -S

Repository: langflow-ai/langflow

Length of output: 38690


Use the Requesty catalog model for the auth probe src/lfx/src/lfx/base/models/unified_models/credentials.py:517-544
This check always sends openai/gpt-4o-mini, ignoring validation_model. A valid Requesty key can fail if that model isn’t in the key’s accessible catalog, and model-specific 4xxs are reported as connectivity failures. Use an allowed model from the authenticated catalog, or pass through validation_model, before probing.

🤖 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 `@src/lfx/src/lfx/base/models/unified_models/credentials.py` around lines 517 -
544, The Requesty authentication probe in the credential validation flow
hardcodes openai/gpt-4o-mini instead of using the configured validation_model.
Update the requests.post payload to use validation_model, or resolve and select
an allowed model from the authenticated catalog before probing, so
model-specific 4xx responses do not incorrectly appear as connectivity failures.

Comment on lines +46 to +65
try:
headers = {"Authorization": f"Bearer {self.api_key}"} if self.api_key else {}
response = httpx.get("https://router.requesty.ai/v1/models", headers=headers, timeout=10.0)
response.raise_for_status()
models = response.json().get("data", [])
return sorted(
[
{
"id": m["id"],
"name": m.get("id"),
"context": m.get("context_window", 0),
}
for m in models
if m.get("id")
],
key=lambda x: x["name"],
)
except (httpx.RequestError, httpx.HTTPStatusError) as e:
self.log(f"Error fetching models: {e}")
return []

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check httpx exception hierarchy for the project's httpx version
python -c "import httpx; print(httpx.__version__); print(httpx.DecodingError.__mro__)"

Repository: langflow-ai/langflow

Length of output: 318


🏁 Script executed:

python3 - <<'PY'
import httpx, inspect
from httpx import Response

print("httpx", httpx.__version__)
print("DecodingError MRO:", httpx.DecodingError.__mro__)
print("Response.json source:")
print(inspect.getsource(Response.json))
PY

Repository: langflow-ai/langflow

Length of output: 475


Catch json.JSONDecodeError in fetch_models.
httpx.DecodingError is already covered by the existing except, but response.json() can still raise json.JSONDecodeError on a malformed 2xx body and escape this handler. Add that exception so the method still returns [] on bad responses.

🤖 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 `@src/lfx/src/lfx/components/requesty/requesty.py` around lines 46 - 65, Update
fetch_models to catch json.JSONDecodeError alongside the existing HTTP
exceptions around response.json(), ensuring malformed successful responses are
logged and return [] without escaping the handler.

Comment on lines +80 to +85
if not self.api_key:
msg = "API key is required"
raise ValueError(msg)
if not self.model_name or self.model_name == "Loading...":
msg = "Please select a model"
raise ValueError(msg)

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

Guard against the "Failed to load models" sentinel value in build_model.

update_build_config sets model_name to "Failed to load models" when fetch_models() returns an empty list (Line 75). However, build_model only rejects "Loading..." and empty strings (Line 83). If the framework applies the build_config value to self.model_name, build_model would proceed to construct a ChatOpenAI with model="Failed to load models", producing a confusing API-level error rather than a clear validation message.

🐛 Proposed fix
-        if not self.model_name or self.model_name == "Loading...":
+        if not self.model_name or self.model_name in ("Loading...", "Failed to load models"):
             msg = "Please select a model"
             raise ValueError(msg)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if not self.api_key:
msg = "API key is required"
raise ValueError(msg)
if not self.model_name or self.model_name == "Loading...":
msg = "Please select a model"
raise ValueError(msg)
if not self.api_key:
msg = "API key is required"
raise ValueError(msg)
if not self.model_name or self.model_name in ("Loading...", "Failed to load models"):
msg = "Please select a model"
raise ValueError(msg)
🤖 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 `@src/lfx/src/lfx/components/requesty/requesty.py` around lines 80 - 85, Update
the model validation in build_model to reject the "Failed to load models"
sentinel in addition to missing values and "Loading...". Preserve the existing
"Please select a model" ValueError message and leave the API-key validation
unchanged.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant