fix(inference): explain background-role provider fallback and verify BYOK keys (#5146 §2.1, §2.4) - #5254
Conversation
…BYOK keys Addresses tinyhumansai#5146 §2.1 and §2.4. §2.1 — spurious cross-provider key errors A user on a local Ollama chat model who attaches an image gets "failed to read API key for slug 'anthropic'", naming a provider they never configured. The cause is `provider_for_role`: the background roles (vision, embeddings, memory, heartbeat, learning, subconscious, agentic, burst) fall through to the primary cloud provider when their own route is unset, and the resulting auth failure surfaces as a bare slug error. That routing is deliberate and stays. Background workloads run tier-specific models (vision-v1, summarization-v1, ...) that local runtimes do not serve, and a local-chat + managed-subscription user genuinely wants them on the cloud. Hard-failing here would break that configuration; users who want no cloud egress at all already have PrivacyMode::LocalOnly, enforced at the inference chokepoint. What was missing is the explanation. New `fallback_diagnostics` module holds the wording as pure, unit-testable functions, wired at two sites: - `provider_for_role` now logs why a background role went to the cloud, naming the local chat model and the config knob that overrides it (`burst` correctly points at `agentic_provider`, which exists, rather than a `burst_provider` that does not). - `resolve_cloud_slug` wraps the key-lookup failure with that context, so the error explains that a background role fell back, why, and what to change — instead of naming an unconfigured provider with no reason. A vision pre-flight logs the actionable "switch to llava:7b" message at the point the session builder decides a model cannot take images, where the turn engine would otherwise strip the image silently. §2.4 — connected but unusable Storing a key only proves it was written to disk, and the existing save probe calls /models, which proves the key and endpoint are valid but not that inference works. Adds `verifyCloudProviderConnection`, which runs a real minimal completion through the existing `openhuman.inference_test_provider_model` RPC and never throws, plus `describeProviderVerificationFailure`, which maps the common upstream shapes (401, model_not_found, quota/429, bare 404, timeout) onto concrete next steps and passes anything unrecognised through verbatim. The manual provider Test action now reports the classified message instead of a raw upstream string. The automatic post-save probe is deliberately not wired yet: the RPC takes a fully-qualified `slug:model` provider string, and guessing one from the provider entry risks a false negative that would roll back a working provider. Tracked in the PR body as follow-up. §2.3 (DeepSeek) is not in this change — see the PR body. Tests: Rust unit tests pin the diagnostics wording, the knob mapping, and the vision pre-flight, plus `provider_for_role` regressions asserting the background-role routing is unchanged and that the drift between the diagnostics role list and the real fallback set is caught. Vitest covers every branch of the failure classifier and the verification result shape.
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 19 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (20)
📝 WalkthroughWalkthroughAdds structured cloud-provider verification and localized failure classification in the settings UI, while centralizing Rust provider fallback diagnostics, credential guidance, routing behavior, regression tests, and vision capability preflight logging. ChangesCloud provider verification
Inference fallback diagnostics
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant AIPanel
participant aiSettingsApi
participant testProviderModel
AIPanel->>aiSettingsApi: request provider verification
aiSettingsApi->>testProviderModel: send ping inference
testProviderModel-->>aiSettingsApi: return reply or rejection
aiSettingsApi-->>AIPanel: return localized verification result
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d5669906cb
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
src/openhuman/agent/harness/session/builder/factory.rs (1)
493-505: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winLog the successful vision-preflight branch.
This new flow only logs failures, so grep-friendly diagnostics cannot distinguish a successful check from a skipped or unexecuted check. Add an entry and success log containing the resolved model, while retaining the actionable error message.
As per coding guidelines, changed Rust flows must include verbose, grep-friendly diagnostics for relevant branches and errors.
🤖 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/openhuman/agent/harness/session/builder/factory.rs` around lines 493 - 505, Update the vision preflight flow around fallback_diagnostics::vision_preflight to log a grep-friendly success entry containing the resolved model_name when validation succeeds, while preserving the existing actionable failure log and its reason.Source: Coding guidelines
🤖 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 `@app/src/components/settings/panels/AIPanel.tsx`:
- Around line 2154-2160: The error handler passes the composite
currentProviderString to describeProviderVerificationFailure instead of the
plain provider slug. Update this call to use the existing registrySlug value,
while preserving the raw error extraction and stale-request guard.
In `@app/src/services/api/aiSettingsApi.ts`:
- Around line 489-527: Update describeProviderVerificationFailure to return
translated messages through the established useT/I18nContext pattern instead of
hardcoded English, preserving all six matching branches and fallback behavior.
Add corresponding translation keys with {slug} and other dynamic details to
en.ts and every locale file, then interpolate slug and error detail via the
existing translation-key convention before AIPanel renders the banner.
- Around line 538-561: Add verbose, grep-friendly diagnostics to
verifyCloudProviderConnection covering function entry, the testProviderModel
external call, successful non-empty response, empty-reply branch, and caught
errors. Use the existing logging conventions from nearby flows such as
listProviderModels, and avoid logging secrets or raw credentials while including
safe provider/workload and error context.
In `@src/openhuman/inference/provider/factory_tests.rs`:
- Around line 1488-1497: Update both all-roles matrices in
src/openhuman/inference/provider/factory_tests.rs: add "summarization" to the
managed-backend fallback assertions at lines 1488-1497 and the
diagnostics/routing consistency assertions at lines 1542-1551, preserving the
existing ordering and assertion behavior.
In `@src/openhuman/inference/provider/factory.rs`:
- Around line 2211-2219: Update the lookup_key_for_slug error branch in the
provider factory to emit a verbose, grep-friendly diagnostic before returning
the guidance error. Include the role, slug, and whether routing used an implicit
fallback, while excluding the underlying error details and all credential values
from logs; preserve the existing returned error message.
- Around line 2212-2218: Update the error construction around
missing_provider_credentials_message so local_chat is passed only for
cloud-fallback roles whose route is unset or explicitly "cloud"; when the role
has an explicitly configured non-cloud route such as vision_provider, pass None.
Preserve the existing guidance generation and underlying error reporting.
---
Nitpick comments:
In `@src/openhuman/agent/harness/session/builder/factory.rs`:
- Around line 493-505: Update the vision preflight flow around
fallback_diagnostics::vision_preflight to log a grep-friendly success entry
containing the resolved model_name when validation succeeds, while preserving
the existing actionable failure log and its reason.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 91249e08-7354-4b34-b383-da2316215bf4
📒 Files selected for processing (8)
app/src/components/settings/panels/AIPanel.tsxapp/src/services/api/__tests__/aiSettingsApi.test.tsapp/src/services/api/aiSettingsApi.tssrc/openhuman/agent/harness/session/builder/factory.rssrc/openhuman/inference/provider/factory.rssrc/openhuman/inference/provider/factory_tests.rssrc/openhuman/inference/provider/fallback_diagnostics.rssrc/openhuman/inference/provider/mod.rs
…sai#5146) Frontend Checks was red on Prettier; that is fixed, and the review items on top of it: i18n (codex P1, CodeRabbit major) — `describeProviderVerificationFailure` returned six hard-coded English sentences that `AIPanel` renders verbatim, so non-English builds showed English and the i18n tooling could not see the copy. Classification is now split from wording: `classifyProviderVerificationFailure` returns a reason code, and the message resolves `settings.ai.providerTest.*` through the translator, following the existing `presentProviderSetupError` idiom. Eight keys added to en.ts and all 13 locale files with real translations, placeholders preserved, no em dashes. Wrong identifier (CodeRabbit major) — AIPanel passed `currentProviderString` (the composite `provider:model[@temp]`) where a bare slug was expected, so the banner read "'openai:gpt-4o' rejected it". It now passes `registrySlug`. Model-qualified verification (codex P2) — the core only builds a configured cloud provider from `<slug>:<model>`, so `verifyCloudProviderConnection` would have failed a valid key when handed a bare slug. It now forwards the composite verbatim and derives the bare slug for the message. Diagnostics (CodeRabbit major/minor) — `verifyCloudProviderConnection` logs entry/empty-reply/ok/failure, and the credential-lookup failure logs role, slug, auth style and whether the route was an implicit fallback. Never the underlying error or the key. Empty BYOK key (codex P2) — a readable auth profile with no key for the slug returns `Ok("")`, which built a client with an empty bearer and surfaced as a raw 401 later: exactly the error this diagnostic replaces. An empty key is now treated as missing for the credentialed auth styles (`OpenhumanJwt` and `None` are legitimately blank). Fallback attribution (CodeRabbit major) — an explicitly configured cloud route that fails key lookup was described as a local-chat fallback. The role→route mapping is factored out of `provider_for_role` into `configured_route_for_role`, and `role_uses_implicit_cloud_fallback` gates the explanation so only a genuine implicit fallback names the local chat model. Tests: `summarization` added to both fallback matrices (CodeRabbit), plus coverage for the empty-key path, the implicit-fallback predicate, the i18n key resolution, and model-qualified verification.
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
| Filename | Overview |
|---|---|
| app/src/services/api/aiSettingsApi.ts | Adds CloudProviderVerification, classifyProviderVerificationFailure, describeProviderVerificationFailure, and verifyCloudProviderConnection. The classifier has one edge-case false positive: 'insufficient' can match permission-denied errors from providers like Groq. |
| src/openhuman/inference/provider/fallback_diagnostics.rs | New pure-function module for actionable fallback diagnostics. Role list, capability labels, override knob mapping (burst→agentic, summarization→memory), and vision preflight are all correct. |
| src/openhuman/inference/provider/factory.rs | Refactors the role→config-field match into configured_route_for_role (no behaviour change), adds role_uses_implicit_cloud_fallback, and wires the diagnostics at the fallback log site and at the empty-key guard. |
| src/openhuman/inference/provider/factory_tests.rs | Adds four routing-regression tests plus a drift guard tying CLOUD_FALLBACK_ROLES to what provider_for_role actually falls back on. |
| app/src/components/settings/panels/AIPanel.tsx | Single change: the raw upstream error in the Test-action catch block is replaced with describeProviderVerificationFailure. The bare slug — not the composite provider:model string — is passed to the classifier, which is correct. |
| src/openhuman/agent/harness/session/builder/factory.rs | Adds vision_preflight call (log-only) after the existing model_supports_vision call. The extra call is intentional (logging at the decision point) and has no semantic impact. |
| app/src/services/api/tests/aiSettingsApi.test.ts | Adds 11 test cases covering every branch of classifyProviderVerificationFailure and verifyCloudProviderConnection including the non-Error rejection path. |
Reviews (3): Last reviewed commit: "fix(inference): scope the empty-key guar..." | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 `@app/src/lib/i18n/en.ts`:
- Around line 5317-5318: Update the settings.ai.providerTest.unknown translation
usage so the {detail} placeholder receives only an allowlisted reason or fixed
unknown-error message, rather than the raw upstream provider/RPC error. Preserve
the existing key-saved and test-failed message behavior while sanitizing the
fallback branch.
In `@app/src/lib/i18n/hi.ts`:
- Around line 4729-4730: Update the `settings.ai.providerTest.unknown`
translation to remove the `{detail}` interpolation and use a generic
verification-failure message. Keep sanitized diagnostic information routed
exclusively through the existing diagnostics path.
In `@app/src/lib/i18n/pl.ts`:
- Around line 4800-4801: Update the settings.ai.providerTest.unknown translation
to use a redacted unknownError-style fallback instead of interpolating the raw
{detail} value, ensuring provider or RPC internals are not surfaced in the UI
while preserving the existing failure message context.
In `@app/src/lib/i18n/ru.ts`:
- Around line 4773-4774: Update the localized settings.ai.providerTest.unknown
message in the Russian translations so it no longer interpolates the raw
{detail} error text; use the localized unknownError fallback or another
sanitized reason while preserving the existing key-saved and test-failed
context.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6968b155-e41d-4191-b9ad-cc2a21e841d4
📒 Files selected for processing (19)
app/src/components/settings/panels/AIPanel.tsxapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsapp/src/services/api/__tests__/aiSettingsApi.test.tsapp/src/services/api/aiSettingsApi.tssrc/openhuman/inference/provider/factory.rssrc/openhuman/inference/provider/factory_tests.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- app/src/components/settings/panels/AIPanel.tsx
- app/src/services/api/tests/aiSettingsApi.test.ts
- src/openhuman/inference/provider/factory.rs
…nyhumansai#5146) Second review pass on the verification messages. Raw error leakage (CodeRabbit, 4 threads) — the fallback branch interpolated the upstream provider/RPC string straight into user-visible copy, which can echo request material (headers, key fragments) into a screenshot-able banner and contradicted this PR's own rule that diagnostics must not carry the underlying error. The message is now generic; the raw text stays on `CloudProviderVerification.detail` for the details surface, and both callers log it (`console.error`) so it is still reachable for diagnosis. `model not found` crossfire (greptile P1) — the endpoint branch matches a bare `not found`, so providers phrasing a missing model as "model not found" or "The model `x` was not found" were told to check their base URL and `/v1` suffix instead of their model id. Classified as a model problem when the text mentions both, which covers the contiguous and the interpolated phrasings. Dead `empty` variant (greptile P2) — removed from `ProviderVerificationReason`: the empty-reply path is handled inside `verifyCloudProviderConnection` and never reaches the classifier, so the variant could only mislead. Locales updated for the new generic wording; `providerTest.unknownError` is gone with the `{detail}` interpolation it existed to fill.
|
@coderabbitai review |
✅ Action performedReview finished.
|
…inyhumansai#5146) CI caught the first pass being too broad: treating a blank key as missing for every Bearer/Anthropic slug failed 8 pre-existing factory tests that build cloud models without a stored key, plus the BYOK subconscious routing test. Callers legitimately construct such models to probe or describe a provider before a key is saved, so failing at construction time was a behaviour change nobody asked for. The guard now fires only on the implicit cloud-fallback path — the case the diagnostic exists for, and the one codex described: a local-chat user whose background role landed on a BYOK slug they never configured. Explicitly routed providers keep their existing behaviour. The test pins both halves. Also fixes the AIPanel test this PR had broken: the panel now imports `describeProviderVerificationFailure`, which the suite's `vi.mock` factory did not stub, so the dialog threw and rendered no alert at all. The mock now mirrors the mapped copy, and the assertion checks the actionable message rather than the raw upstream string the UI no longer shows. (Frontend Checks had been dying at Prettier before reaching vitest, which is why this surfaced only now.)
|
@coderabbitai review |
✅ Action performedReview finished.
|
Stream B of #5146 — provider detection and routing bugs.
Covered here: §2.1 Spurious API key errors for unrelated providers and §2.4 Codex / GPT BYOK — connected but unusable.
Deferred: §2.3 (DeepSeek) — see Deferred work below.
Summary
failed to read API key for slug 'anthropic', naming a provider they never configured. Fixed by explaining the fallback, not by changing where requests route.fallback_diagnosticsmodule carries the user-facing wording as pure, unit-testable functions, wired at the two sites named in triage plus a vision pre-flight.verifyCloudProviderConnection) over the existinginference_test_provider_modelRPC, and a classifier that maps opaque upstream errors onto concrete next steps.burstusers to setburst_provider, a setting that does not exist.Problem
§2.1 — why an unconfigured provider is asked for a key
provider_for_role(src/openhuman/inference/provider/factory.rs) routes the background roles —vision,embeddings,memory,summarization,heartbeat,learning,subconscious,agentic,burst— to the primary cloud provider when their own route is unset. Onlychat,reasoningandcodinginherit a configured BYOK slug.So: chat is
ollama:gemma3:1b, the user attaches an image, the vision sub-agent runs withrole="vision",vision_provideris unset → resolves to the primary cloud slug →lookup_key_for_slugfails → a bare slug-level auth error naming Anthropic.The routing is correct and is kept. Background workloads run tier-specific models (
vision-v1,summarization-v1, …) that local runtimes do not serve, and a user with a local chat model and a managed subscription genuinely wants those workloads on the cloud they are paying for. Hard-failing would break that configuration. Users who want zero cloud egress already havePrivacyMode::LocalOnly, enforced at the inference chokepoint byenforce_local_only_inference— this PR deliberately does not duplicate that gate.What was actually broken is that the fallback was unexplained: nothing told the user that this was a background role, that it fell back because the local model cannot serve that capability, or what to change.
§2.4 — "connected" only ever meant "the key was written to disk"
setCloudProviderKeystores a credential. The save flow's existing probe callslistProviderModels(/models), which proves the key is valid and the endpoint reachable — but not that inference works. A provider with a wrong default model id, an exhausted quota, or a base URL missing/v1lists models happily and then fails on the user's first real message.Solution
§2.1 (Option A — explain, don't reroute)
New
src/openhuman/inference/provider/fallback_diagnostics.rs. Every function is a pure string/predicate so the wording and the role rules are testable without a live provider. Wired at three sites:provider_for_role[providers][local-fallback]log now emits a full sentence: which capability, which local chat model caused the fallback, which provider it landed on, and the knob that overrides it.resolve_cloud_slugsession/builder/factory.rsoverride_knob_for_rolemapsburst → agenticandsummarization → memory, so the message never points at a setting that does not exist. The old code emittedset burst_provider explicitly to override; there is no such field.§2.4
verifyCloudProviderConnection(slug, workload)— runs one real minimal completion throughopenhuman.inference_test_provider_model. Never throws: a provider that cannot serve a request is a result, so the caller can surface it without losing the save. An empty reply counts as unusable.describeProviderVerificationFailure(slug, raw)— maps the common upstream shapes onto concrete next steps: auth rejection → check the key;model_not_found→ fix the model id; quota/429→ check balance; bare404→ check the base URL for the/v1suffix; timeout → check endpoint/network. Anything unrecognised passes through verbatim rather than inventing a diagnosis.Impact
provider_for_roleregression tests pin this so a future "never fall back" refactor cannot silently break local-chat + managed-subscription users.infoline per background-role resolution, only when the chat provider is local.Deferred work
cloud_providers.rspreset (https://api.deepseek.com/v1) routed through the generic OpenAI-compatible client, so its known quirks (reasoning_contenton R-series, tool-call streaming deltas) would be unhandled. That is a hypothesis, not a diagnosis: confirming it needs a real DeepSeek key or a captured failing response body. Writing a speculative response adapter against a guess would silently reshape live provider output, which is worse than shipping nothing. Deferred pending a key or a captured response./modelsprobe rolls back the key and the provider entry on failure, and hooking an inference call into the same validation needs a fully-qualifiedslug:modelprovider string. The frontendCloudProvidershape does not carry a default model, so deriving one would risk a false negative that destroys a working provider config — a worse bug than the one being fixed. Left as follow-up rather than guessed at.Related
N/A: #5146 has further parts (1.x, 2.2, 2.3, 3, 4, 5) outside this stream, so it must stay open.Submission Checklist
provider_for_roleregressions (including a drift guard tying the diagnostics role list to the real fallback set); 11 Vitest cases covering every branch of the failure classifier and the verification result shape, including the non-Errorrejection path.pnpm installin a fresh worktree. CI'sfrontend-coverage/rust-core-coverage/coverage-gatejobs are the arbiter.N/A: diagnostics/error-message change, no new feature row.## Related—N/A: no feature IDs affected.verifyCloudProviderConnectionreuses the existinginference_test_provider_modelRPC; no new endpoints.N/A: not a release-cut surface.Closes #NNN—N/A: #5146 stays open, see Related.AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
fix/5146-stream-b-provider-routingd5669906cValidation Run
pnpm --filter openhuman-app format:check— not run locally; see Validation Blocked.pnpm typecheck— not run locally; see Validation Blocked.factory_tests.rs,fallback_diagnostics.rsinline tests,aiSettingsApi.test.ts.N/A: app/src-tauri unchanged.Validation Blocked
command:cargo check/cargo test/pnpm testerror:cargo commands were prohibited by the task directive for this change; Vitest needs apnpm installin a fresh git worktree.impact:no compiler or test feedback was available locally. One path bug was found and fixed by inspection —factory_testsis a child module offactory, sosuper::fallback_diagnosticsdoes not resolve there and the crate-absolute path is used instead.vision_preflightwas wired to a real call site rather than left test-only so it cannot trip a dead-code deny. Reviewers may want to look hardest at theOption::filterclosure in themap_erratresolve_cloud_slug(relies on&&str → &strderef coercion) and the format-string argument counts in the new module.Behavior Changes
failed to read API key for slug 'anthropic', a local-model user is told that a background workload fell back to the cloud, why, and which setting to change.Parity Contract
provider_for_rolereturns identical strings for every role and config; the chat-tier BYOK inheritance, theLocalOnlyprivacy gate, and the save-flow/modelsprobe and its rollback are untouched.local_chat_still_routes_background_roles_to_the_managed_backend,local_chat_role_is_returned_verbatim_and_never_falls_back,explicit_background_route_overrides_the_cloud_fallback, andcloud_fallback_roles_match_the_roles_provider_for_role_actually_falls_back.Duplicate / Superseded PR Handling
Summary by CodeRabbit