feat: add Requesty as a model provider#14020
Conversation
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>
WalkthroughAdds 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. ChangesRequesty provider integration
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
Possibly related PRs
Suggested labels: Suggested reviewers: Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error, 2 warnings)
✅ Passed checks (6 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (15)
src/frontend/src/icons/Requesty/RequestyIcon.jsxsrc/frontend/src/icons/Requesty/index.tsxsrc/frontend/src/icons/eagerIconImports.tssrc/frontend/src/icons/lazyIconImports.tssrc/lfx/src/lfx/base/models/model_metadata.pysrc/lfx/src/lfx/base/models/model_utils.pysrc/lfx/src/lfx/base/models/requesty_constants.pysrc/lfx/src/lfx/base/models/unified_models/credentials.pysrc/lfx/src/lfx/base/models/unified_models/instantiation.pysrc/lfx/src/lfx/base/models/unified_models/provider_queries.pysrc/lfx/src/lfx/components/__init__.pysrc/lfx/src/lfx/components/requesty/__init__.pysrc/lfx/src/lfx/components/requesty/requesty.pysrc/lfx/src/lfx/services/settings/constants.pysrc/lfx/tests/unit/base/models/test_requesty_provider.py
| export const RequestyIcon = forwardRef< | ||
| SVGSVGElement, | ||
| React.PropsWithChildren<{}> | ||
| >((props, ref) => { | ||
| return <SvgRequesty ref={ref} {...props} />; |
There was a problem hiding this comment.
🎯 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' || trueRepository: 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' || trueRepository: 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.tsRepository: 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
| 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; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n 'forwardRef|isDark' src/frontend/src/icons/RequestyRepository: 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 20Repository: 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 200Repository: 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"
doneRepository: 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
| 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]) |
There was a problem hiding this comment.
🩺 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.
| 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.
| # 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}" |
There was a problem hiding this comment.
🎯 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 -SRepository: 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 80Repository: 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 -SRepository: 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.
| 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 [] |
There was a problem hiding this comment.
🩺 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))
PYRepository: 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.
| 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) |
There was a problem hiding this comment.
🎯 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.
| 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.
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 URLhttps://router.requesty.ai/v1,REQUESTY_API_KEY,provider/modelnaming, optionalHTTP-Referer/X-Titleattribution headers)base/models/requesty_constants.py— seed catalog shown before a key is configuredbase/models/model_utils.py—fetch_live_requesty_modelsfor the live/v1/modelscatalog. Requesty's payload differs from OpenRouter's: capabilities are flat booleans (supports_tool_calling/supports_reasoning) rather than asupported_parametersarray, and the context field iscontext_window(notcontext_length) — the fetcher maps the real fields.base/models/model_metadata.py—MODEL_PROVIDER_METADATA["Requesty"]+LIVE_MODEL_PROVIDERSbase/models/unified_models/credentials.py— key validation via a 1-token/v1/chat/completionsprobe. Requesty's/v1/modelsis 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-compatibleget_llmpath alongside OpenRouterbase/models/unified_models/provider_queries.py,components/__init__.py,services/settings/constants.py— registration +REQUESTY_*env-var auto-importFrontend
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, andget_llmbase-URL + attribution-header wiring), mirroringtest_openrouter_provider.pyruff checkclean on all touched Python filesbuild_model()path againsthttps://router.requesty.ai/v1(modelopenai/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