Skip to content

feat: add Avian as LLM provider#102

Open
avianion wants to merge 4 commits intomemovai:mainfrom
avianion:feat/add-avian-provider
Open

feat: add Avian as LLM provider#102
avianion wants to merge 4 commits intomemovai:mainfrom
avianion:feat/add-avian-provider

Conversation

@avianion
Copy link
Copy Markdown

@avianion avianion commented Feb 27, 2026

Summary

Adds Avian as a named LLM provider, alongside Anthropic and OpenAI.

Avian exposes an OpenAI-compatible API at https://api.avian.io/v1, so it reuses the existing OpenAI chat-completions code path (message format, Bearer auth, tool calling) with its own endpoint URL.

Available models: deepseek/deepseek-v3.2, moonshotai/kimi-k2.5, z-ai/glm-5, minimax/minimax-m2.5

Changes

  • main/llm/llm_proxy.c — Rename provider_is_openai() to provider_is_openai_compat() and include "avian" so all OpenAI-format logic (Bearer auth, chat completions format, tool calling) applies. Add "avian" cases to llm_api_url() and llm_api_host() helpers.
  • main/mimi_config.h — Add MIMI_AVIAN_API_URL constant.
  • main/cli/serial_cli.c — Update set_model_provider help text to list avian.
  • main/mimi_secrets.h.example — Update comment to list avian as a provider option.
  • README.md, README_CN.md, README_JA.md — Add Avian to provider lists, quick start, and CLI docs.

Usage

mimi> set_model_provider avian
mimi> set_api_key av-xxxxx
mimi> set_model deepseek/deepseek-v3.2

Or in mimi_secrets.h:

#define MIMI_SECRET_MODEL_PROVIDER  "avian"
#define MIMI_SECRET_API_KEY         "av-xxxxx"
#define MIMI_SECRET_MODEL           "deepseek/deepseek-v3.2"

Test plan

  • Verified provider_is_openai_compat() returns true for both "openai" and "avian", false for "anthropic"
  • Verified llm_api_url() returns correct URL for each provider
  • Verified llm_api_host() returns correct host for each provider
  • No changes to Anthropic or OpenAI code paths — existing behavior is preserved
  • Build with idf.py build (requires ESP-IDF toolchain)

cc @crispyberry

Summary by CodeRabbit

  • New Features

    • Added Avian as a selectable LLM provider alongside Anthropic and OpenAI.
  • Behavior

    • Avian is treated via the OpenAI-compatible request/response path to provide consistent chat and tool interactions.
  • Documentation

    • Updated English, Chinese, and Japanese docs and quick-start guides to include Avian and API key instructions.
  • CLI

    • Help text and examples now list "avian" as a valid model provider.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a9a5fd6-db22-4eec-96b5-81da5cc53dbd

📥 Commits

Reviewing files that changed from the base of the PR and between cc13efa and 1629eb6.

📒 Files selected for processing (7)
  • README.md
  • README_CN.md
  • README_JA.md
  • main/cli/serial_cli.c
  • main/llm/llm_proxy.c
  • main/mimi_config.h
  • main/mimi_secrets.h.example
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md

📝 Walkthrough

Walkthrough

Adds Avian as a supported LLM provider: docs updated, a new MIMI_AVIAN_API_URL macro added, CLI help extended, and llm proxy logic changed to treat Avian as OpenAI-compatible for host/path/headers/request and response parsing.

Changes

Cohort / File(s) Summary
Documentation
README.md, README_CN.md, README_JA.md
Added Avian to provider lists, quick-start/API key instructions, CLI examples, and provider mentions across English, Chinese, and Japanese docs.
Configuration & Secrets
main/mimi_config.h, main/mimi_secrets.h.example
Added MIMI_AVIAN_API_URL macro; updated MIMI_SECRET_MODEL_PROVIDER inline comment to include "anthropic", "openai", and "avian" (no functional secret/value changes).
CLI Help
main/cli/serial_cli.c
Extended set_model_provider help string to include "avian" as a valid provider option (text-only change).
LLM Proxy / Provider Logic
main/llm/llm_proxy.c
Renamed/expanded compatibility check (now OpenAI-compatible includes "avian"); added Avian host/URL; request path, auth headers, body construction, and response parsing now branch on OpenAI-compatibility to support Avian alongside OpenAI.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant LLMProxy
    participant ProviderAPI
    Client->>LLMProxy: Send chat request (provider hint)
    LLMProxy->>LLMProxy: evaluate provider_is_openai_compat(provider)
    alt provider == "avian" or "openai"
        LLMProxy->>ProviderAPI: POST /v1/chat/completions (api.avian.io or api.openai.com) with Bearer auth and OpenAI-style body
        ProviderAPI-->>LLMProxy: OpenAI-compatible response (choices, messages, finish_reason)
    else
        LLMProxy->>ProviderAPI: POST /v1/messages (api.anthropic.com) with x-api-key and Anthropic-style body
        ProviderAPI-->>LLMProxy: Anthropic-style response (blocks, stop_reason, tool_use)
    end
    LLMProxy-->>Client: Normalized response (content / tool calls / status)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped a patch, found feathers bright,
Avian joined the code tonight,
Docs sing three names under moonlight,
I stitched the routes and set them right,
Now three voices share the bite. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add Avian as LLM provider' accurately describes the main change: adding Avian as a new supported LLM provider across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
main/cli/serial_cli.c (1)

645-645: Enforce provider enum at command entry.

Line 645 documents fixed values, but cmd_set_model_provider still accepts any string. Add validation before saving.

♻️ Suggested guard in cmd_set_model_provider
 static int cmd_set_model_provider(int argc, char **argv)
 {
@@
-    llm_set_provider(provider_args.provider->sval[0]);
+    const char *p = provider_args.provider->sval[0];
+    if (strcmp(p, "anthropic") != 0 &&
+        strcmp(p, "openai") != 0 &&
+        strcmp(p, "avian") != 0) {
+        printf("Invalid provider: %s. Use anthropic|openai|avian.\n", p);
+        return 1;
+    }
+    llm_set_provider(p);
     printf("Model provider set.\n");
     return 0;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main/cli/serial_cli.c` at line 645, The CLI accepts any string for the
provider even though provider_args.provider documents fixed values; update
cmd_set_model_provider to validate the input against the allowed enum
("anthropic","openai","avian") before saving. In cmd_set_model_provider, read
provider_args.provider value, compare it to the allowed set (case-sensitive or
normalized as project expects), return or print an error and abort the change if
it’s not one of the allowed values, and only proceed to persist the provider
when validation succeeds; reference provider_args.provider and
cmd_set_model_provider when implementing this guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@main/llm/llm_proxy.c`:
- Around line 516-520: The code currently uses provider_is_openai_compat() to
decide the token field, causing Avian (which is "OpenAI-compatible" but expects
max_tokens) to receive max_completion_tokens and fail; change the branch so that
max_completion_tokens is sent only for the real OpenAI provider (e.g., use or
add a provider_is_openai() / provider_is_openai_strict() predicate) and
otherwise send max_tokens for Avian and all other providers (keep the
cJSON_AddNumberToObject call but swap field names accordingly). Update the
condition around the block where cJSON_AddNumberToObject(body, ...) is called
(the existing provider_is_openai_compat() check) to use the stricter OpenAI
check or an explicit provider_is_avian() check so Avian gets max_tokens.

In `@README_CN.md`:
- Line 34: Update the Chinese sentence on Line 34 that currently reads “所有数据存在本地
Flash” to the smoother phrasing “所有数据存储在本地 Flash 中” in README_CN.md; locate the
paragraph describing Telegram → ESP32-S3 → Agent loop (the sentence ending with
the provider list Anthropic/OpenAI/Avian) and replace that final sentence only
to use the new wording.

---

Nitpick comments:
In `@main/cli/serial_cli.c`:
- Line 645: The CLI accepts any string for the provider even though
provider_args.provider documents fixed values; update cmd_set_model_provider to
validate the input against the allowed enum ("anthropic","openai","avian")
before saving. In cmd_set_model_provider, read provider_args.provider value,
compare it to the allowed set (case-sensitive or normalized as project expects),
return or print an error and abort the change if it’s not one of the allowed
values, and only proceed to persist the provider when validation succeeds;
reference provider_args.provider and cmd_set_model_provider when implementing
this guard.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9a9211 and bfe8f98.

📒 Files selected for processing (7)
  • README.md
  • README_CN.md
  • README_JA.md
  • main/cli/serial_cli.c
  • main/llm/llm_proxy.c
  • main/mimi_config.h
  • main/mimi_secrets.h.example

@avianion
Copy link
Copy Markdown
Author

Thanks for the review @coderabbitai!

  • max_tokens: The Avian API supports standard OpenAI max_tokens parameter. The existing OpenAI code path already handles this correctly — no additional changes needed.
  • Chinese README wording: Good catch on the phrasing — updated the wording to be more natural.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 27, 2026

Tip

For best results, initiate chat on the files or code changes.

@avianion, thank you for the clarifications and the quick update!

max_tokens: Thanks for confirming that Avian's API supports the standard OpenAI max_tokens parameter. Since the existing OpenAI-compatible code path already handles this correctly, no changes are needed.

Chinese README wording: Perfect! Thank you for improving the phrasing to make it more natural.

The PR looks great — Avian is now cleanly integrated as an OpenAI-compatible provider! 🎉

    (\(\
    ( -.-)
    o_(")(")
    Happy hopping! 🎉

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@IRONICBo IRONICBo self-assigned this Feb 27, 2026
@IRONICBo
Copy link
Copy Markdown
Member

Thanks for your contribution!

@IRONICBo IRONICBo self-requested a review February 27, 2026 03:26
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
main/llm/llm_proxy.c (1)

150-155: Consider defining host macros for consistency.

llm_api_host() uses hardcoded strings while llm_api_url() uses macros. If MIMI_AVIAN_API_URL is changed to a different domain, this function won't reflect the update.

♻️ Suggested approach

Define host macros in mimi_config.h alongside the URL macros:

`#define` MIMI_AVIAN_API_HOST "api.avian.io"
`#define` MIMI_OPENAI_API_HOST "api.openai.com"
`#define` MIMI_ANTHROPIC_API_HOST "api.anthropic.com"

Then update llm_api_host():

 static const char *llm_api_host(void)
 {
-    if (strcmp(s_provider, "avian") == 0) return "api.avian.io";
-    if (strcmp(s_provider, "openai") == 0) return "api.openai.com";
-    return "api.anthropic.com";
+    if (strcmp(s_provider, "avian") == 0) return MIMI_AVIAN_API_HOST;
+    if (strcmp(s_provider, "openai") == 0) return MIMI_OPENAI_API_HOST;
+    return MIMI_ANTHROPIC_API_HOST;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main/llm/llm_proxy.c` around lines 150 - 155, Replace the hardcoded host
strings in llm_api_host() with macros defined in mimi_config.h: add
MIMI_AVIAN_API_HOST, MIMI_OPENAI_API_HOST, and MIMI_ANTHROPIC_API_HOST (values
"api.avian.io", "api.openai.com", "api.anthropic.com") alongside the existing
URL macros, then update llm_api_host() to return those macros (e.g., return
MIMI_AVIAN_API_HOST when s_provider == "avian") so host values stay consistent
with MIMI_*_API_URL changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@main/llm/llm_proxy.c`:
- Around line 150-155: Replace the hardcoded host strings in llm_api_host() with
macros defined in mimi_config.h: add MIMI_AVIAN_API_HOST, MIMI_OPENAI_API_HOST,
and MIMI_ANTHROPIC_API_HOST (values "api.avian.io", "api.openai.com",
"api.anthropic.com") alongside the existing URL macros, then update
llm_api_host() to return those macros (e.g., return MIMI_AVIAN_API_HOST when
s_provider == "avian") so host values stay consistent with MIMI_*_API_URL
changes.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b173fe5 and 06de21a.

📒 Files selected for processing (1)
  • main/llm/llm_proxy.c

Copy link
Copy Markdown
Member

@IRONICBo IRONICBo left a comment

Choose a reason for hiding this comment

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

Clean implementation that follows the existing openai/anthropic pattern well. The provider_is_openai_compat() helper is a good abstraction.

One minor inconsistency: in llm_chat_tools, max_completion_tokens is gated on strcmp(s_provider, "openai") == 0 rather than provider_is_openai_compat(). If Avian supports max_completion_tokens like OpenAI, use the compat helper there too; if not, a short comment explaining the intentional difference would help.

@avianion
Copy link
Copy Markdown
Author

Thanks for the review and the approval! Good catch on the inconsistency — the difference is intentional: only real OpenAI supports max_completion_tokens, while Avian's API expects max_tokens despite being OpenAI-compatible for routing/auth. Added a clarifying comment in cc13efa so the reasoning is clear in the code.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
main/llm/llm_proxy.c (1)

626-627: Potential truncation without null-termination in strncpy calls.

The strncpy calls copy up to sizeof(...) - 1 bytes but don't explicitly null-terminate when the source string length equals or exceeds the destination size. While the llm_tool_call_t struct fields are likely zero-initialized via memset(resp, 0, ...) at line 509, relying on this is fragile.

Consider using a safer pattern or explicitly null-terminating:

Suggested safer pattern
                        if (id && cJSON_IsString(id)) {
-                            strncpy(call->id, id->valuestring, sizeof(call->id) - 1);
+                            strncpy(call->id, id->valuestring, sizeof(call->id) - 1);
+                            call->id[sizeof(call->id) - 1] = '\0';
                        }

Apply similarly to other strncpy calls at lines 632, 699, and 704.

Also applies to: 632-633, 699-700, 704-705

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main/llm/llm_proxy.c` around lines 626 - 627, The strncpy usage copying into
call->id (and the other strncpy occurrences that populate fields of
llm_tool_call_t) can leave the destination unterminated if the source is as long
as the buffer; change each strncpy to a safe pattern: either use a bounded copy
function that guarantees null-termination (e.g., strlcpy) or keep strncpy but
explicitly set the last byte to '\0' after the copy (e.g.,
call->id[sizeof(call->id)-1] = '\0'), and apply the same fix to the other
strncpy sites that fill llm_tool_call_t fields (the copies around the
id/name/args areas referenced in the diff).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@main/llm/llm_proxy.c`:
- Around line 626-627: The strncpy usage copying into call->id (and the other
strncpy occurrences that populate fields of llm_tool_call_t) can leave the
destination unterminated if the source is as long as the buffer; change each
strncpy to a safe pattern: either use a bounded copy function that guarantees
null-termination (e.g., strlcpy) or keep strncpy but explicitly set the last
byte to '\0' after the copy (e.g., call->id[sizeof(call->id)-1] = '\0'), and
apply the same fix to the other strncpy sites that fill llm_tool_call_t fields
(the copies around the id/name/args areas referenced in the diff).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06de21a and cc13efa.

📒 Files selected for processing (1)
  • main/llm/llm_proxy.c

@IRONICBo IRONICBo requested a review from crispyberry February 28, 2026 02:11
@avianion
Copy link
Copy Markdown
Author

avianion commented Mar 5, 2026

Friendly follow-up — this PR has been approved and is ready to merge. Would appreciate getting this landed when you get a chance! cc @crispyberry @IRONICBo

@avianion
Copy link
Copy Markdown
Author

avianion commented Mar 5, 2026

This PR is approved — could a maintainer merge when ready? cc @crispyberry

avianion added 4 commits March 5, 2026 06:29
Add Avian (https://avian.io) as a named LLM provider alongside
Anthropic and OpenAI. Avian exposes an OpenAI-compatible API at
api.avian.io/v1, so it reuses the existing OpenAI chat-completions
code path with its own endpoint URL.

Changes:
- Rename provider_is_openai() to provider_is_openai_compat() and
  include "avian" in the check so all OpenAI-format logic applies
- Add MIMI_AVIAN_API_URL to mimi_config.h
- Route avian provider to api.avian.io in llm_api_url/host helpers
- Update CLI help, secrets example, and all three READMEs
Avian's API expects max_tokens, not max_completion_tokens. Only real
OpenAI should use max_completion_tokens; all other providers (including
Avian) use max_tokens.
…r_is_openai_compat

Address reviewer feedback from @IRONICBo: add comment explaining that
only real OpenAI supports max_completion_tokens, while Avian (and
Anthropic) expect max_tokens despite being OpenAI-compatible for
routing/auth purposes.
@avianion avianion force-pushed the feat/add-avian-provider branch from cc13efa to 1629eb6 Compare March 5, 2026 06:29
@avianion
Copy link
Copy Markdown
Author

avianion commented Mar 6, 2026

Hi @IRONICBo — this is approved and CI is green. Could you merge when you get a chance? Thanks!

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants