Open AI API compatiable LLM server for Amlogic Device
Project description
AML LLM OpenAI Server
An OpenAI-compatible API server for Amlogic LLM devices, with built-in tool-calling evaluation and Agent capability benchmarking tools.
- Server: OpenAI-compatible
/v1/chat/completionsendpoint (ADLA & llama.cpp backends) - Tools Evaluation: Measure tool-calling accuracy of local vs cloud models
- Agent Benchmark: Evaluate LLM agent capabilities on MMLU-Pro, TAU2-Bench, and BFCL
Prerequisites
This project is using uv please use following command or follow the official document to install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
Install the wheel
uv venv --python 3.11
uv pip install amlllm-openai-server -i <index url>
Bootstrap
Use a bootstrap tool
You need Internet connection to use bootstrap scripts to download the models or you can manually put files in folder
- List the available model
uv run -m amlllm_openai_server.bootstrap list
You will see the available models as following table
| # | Name | Type | Thinking | Multimodal | Context | Tools |
|---|---|---|---|---|---|---|
| 0 | Qwen3-1.7B | Qwen3 | Yes | No | 8192 | Yes |
| 1 | Qwen3-4B-Instruct-2507 | Qwen3 | No | No | 8192 | Yes |
| 2 | Qwen3-4B | Qwen3 | Yes | No | 4096 | Yes |
- Download the model
Replace the model name as your expectation. Here use Qwen3-4B-Instruct-2507 for example.
uv run -m amlllm_openai_server.bootstrap download Qwen3-4B-Instruct-2507
Server Configuration
config/server.yaml
server:
host: "0.0.0.0"
port: 8000
api_key: "" # optional, set for authentication
log_level: "info"
title: "AMLLLM OneAPI Proxy"
version: "0.2.0"
models:
root_dir: "../models"
enabled:
- default # model directories under root_dir
# Optional: upstream proxy to a cloud API
# upstream:
# base_url: "https://api.openai.com"
# api_key: "sk-..."
# model: "gpt-4o"
# force_upstream: false
# skill_injection: false
models/default/model.json
Example for Qwen3 Seriers:
{
"id": "amlogic-default",
"weights": "Qwen3-1.7B-Q4AM_PB32.adla",
"model_type": "qwen",
"sampling_mode": "top_p",
"top_k": 40,
"top_p": 0.85,
"temperature": 0.7,
"repeat_penalty": 1.15,
"presence_penalty": 10,
"system_prompt": "You are a helpful assistant.",
"prompt_prefix": "",
"prompt_postfix": "",
"retain_history": true,
"loglevel": "ERROR",
"metadata": {
"family": "default",
"device": "amlogic"
},
"token_pair": { "151657": 151658 },
"chat_format": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].role == 'system' %}\n {{- messages[0].content + '\\n\\n' }}\n {%- endif %}\n {{- \"# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0].content + '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}\n{%- for message in messages[::-1] %}\n {%- set index = (messages|length - 1) - loop.index0 %}\n {%- if ns.multi_step_tool and message.role == \"user\" and message.content is string and not(message.content.startswith('<tool_response>') and message.content.endswith('</tool_response>')) %}\n {%- set ns.multi_step_tool = false %}\n {%- set ns.last_query_index = index %}\n {%- endif %}\n{%- endfor %}\n{%- for message in messages %}\n {%- if message.content is string %}\n {%- set content = message.content %}\n {%- else %}\n {%- set content = '' %}\n {%- endif %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) %}\n {{- '<|im_start|>' + message.role + '\\n' + content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {%- set reasoning_content = '' %}\n {%- if message.reasoning_content is string %}\n {%- set reasoning_content = message.reasoning_content %}\n {%- else %}\n {%- if '</think>' in content %}\n {%- set reasoning_content = content.split('</think>')[0].rstrip('\\n').split('<think>')[-1].lstrip('\\n') %}\n {%- set content = content.split('</think>')[-1].lstrip('\\n') %}\n {%- endif %}\n {%- endif %}\n {%- if loop.index0 > ns.last_query_index %}\n {%- if loop.last or (not loop.last and reasoning_content) %}\n {{- '<|im_start|>' + message.role + '\\n<think>\\n' + reasoning_content.strip('\\n') + '\\n</think>\\n\\n' + content.lstrip('\\n') }}\n {%- else %}\n {{- '<|im_start|>' + message.role + '\\n' + content }}\n {%- endif %}\n {%- else %}\n {{- '<|im_start|>' + message.role + '\\n' + content }}\n {%- endif %}\n {%- if message.tool_calls %}\n {%- for tool_call in message.tool_calls %}\n {%- if (loop.first and content) or (not loop.first) %}\n {{- '\\n' }}\n {%- endif %}\n {%- if tool_call.function %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {%- if tool_call.arguments is string %}\n {{- tool_call.arguments }}\n {%- else %}\n {{- tool_call.arguments | tojson }}\n {%- endif %}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.first or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n {%- if enable_thinking is defined and enable_thinking is false %}\n {{- '<think>\\n\\n</think>\\n\\n' }}\n {%- endif %}\n{%- endif %}"
}
Model Configuration Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id |
string |
Yes | — | Unique model identifier, used as model in OpenAI API requests |
weights |
string |
Yes | — | Path to the model weights file (.adla for ADLA, .gguf for llama.cpp) |
model_type |
string |
Yes | — | Model architecture type: "qwen", "llama", "gemma", etc. Determines chat template and tokenization |
sampling_mode |
string |
No | "top_p" |
Sampling strategy: "top_p", "top_k", or "greedy" |
top_k |
int |
No | 40 |
Top-K sampling: keep only the K most likely tokens |
top_p |
float |
No | 0.85 |
Nucleus sampling: keep tokens with cumulative probability ≥ p |
temperature |
float |
No | 0.7 |
Controls randomness; higher = more creative, lower = more deterministic |
repeat_penalty |
float |
No | 1.15 |
Penalizes repeated tokens; > 1.0 discourages repetition |
presence_penalty |
float |
No | 0 |
ADLA-specific presence penalty for token repetition |
system_prompt |
string |
No | "" |
Default system prompt prepended to every conversation |
prompt_prefix |
string |
No | "" |
Custom text prepended before the prompt |
prompt_postfix |
string |
No | "" |
Custom text appended after the prompt |
retain_history |
bool |
No | true |
Whether to keep conversation history across turns |
loglevel |
string |
No | "ERROR" |
ADLA runtime log level: "DEBUG", "INFO", "WARNING", "ERROR" |
metadata |
dict |
No | {} |
Arbitrary key-value metadata exposed in /v1/models response |
token_pair |
dict |
No | {} |
Special token ID mapping, e.g. {"151657": 151658} maps <tool_call> → </tool_call>, the token MUST appear in pair, as 151657 ->xxx -> 151658, otherwise, the generation will be stopped. |
chat_format |
string |
No | "" |
Jinja2 chat template for formatting messages (see Jinja2 Templates) or Path to tokenizer_config.json |
chat_template_kwargs |
dict |
No | {} |
Extra parameters passed to chat templates |
Jinja2 Chat Templates
The chat_format field uses Jinja2 syntax or tokenizer config to define how messages are formatted into the model's native prompt structure.
- Prepare the model
tokenizer_config.json - For Qwen series, it is possible to put the Jinja template string directly
- For other series, put the path to
tokenizer_config.json
Backend values:
"adla"— Amlogic ADLA runtime (AARCH64 only)
Running the Server
# Default: reads config/server.yaml
uv run python -m amlllm_openai_server
# Custom config path
uv run python -m amlllm_openai_server --config /path/to/server.yaml
# Override host/port/log-level
uv run python -m amlllm_openai_server --host 0.0.0.0 --port 8000 --log-level debug
Test the endpoint:
curl http://localhost:8000/v1/models
curl http://localhost:8000/healthz
If you want to install PicoClaw
dpkg -i picoclaw_modified_aarch64.deb
Run demo with PicoClaw
- Start the AMLLLM server (see above)
- Start PicoClaw:
picoclaw-launcher &
- Configure PicoClaw model:
- Provider: openai
- Model identifier:
amlogic-default - API Base URL:
http://localhost:8000/v1
Tips: The agent work folder is under $HOME/.picoclaw/workspace. If you are using
local models with context limitations, it's better to remove AGENTS.md and SOUL.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file amlllm_openai_server-0.1.1-cp313-cp313-manylinux_2_34_x86_64.whl.
File metadata
- Download URL: amlllm_openai_server-0.1.1-cp313-cp313-manylinux_2_34_x86_64.whl
- Upload date:
- Size: 12.5 MB
- Tags: CPython 3.13, manylinux: glibc 2.34+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7c7dea9d2f1a3b8c8362c4783cfc5eac6c168f3fb6e9a3e8dc9ff95f32240c02
|
|
| MD5 |
e8e08de8e65e0d15adc1ac326a9bac1a
|
|
| BLAKE2b-256 |
1aa75d899c0b04c1e85c4b5ceb9e1a8727cab59a11c58049512782847d0a51a0
|
File details
Details for the file amlllm_openai_server-0.1.1-cp313-cp313-manylinux_2_34_aarch64.whl.
File metadata
- Download URL: amlllm_openai_server-0.1.1-cp313-cp313-manylinux_2_34_aarch64.whl
- Upload date:
- Size: 3.2 MB
- Tags: CPython 3.13, manylinux: glibc 2.34+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7143d89d05964b00f8f851fae9913ba3fe071d81a290a8e113ff5bd67e25b95f
|
|
| MD5 |
8b1776c59c0ebe348659c427359d56a5
|
|
| BLAKE2b-256 |
9541e3898d2df86f5c733c05e8e3f60735992c3b4945b5ba393605af8535f9c0
|
File details
Details for the file amlllm_openai_server-0.1.1-cp312-cp312-manylinux_2_34_x86_64.whl.
File metadata
- Download URL: amlllm_openai_server-0.1.1-cp312-cp312-manylinux_2_34_x86_64.whl
- Upload date:
- Size: 12.6 MB
- Tags: CPython 3.12, manylinux: glibc 2.34+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
94ffd6b24d7a922a8d512e4005c366bdae56bd68a6ae4c95a1cc8ebf5b1b0be2
|
|
| MD5 |
9e6d113fefad09ebfb0ccbcc1e8e0e22
|
|
| BLAKE2b-256 |
e48365b7b1c522965fa70842f2c2decf27deb8a830f96356e11b940d03241094
|
File details
Details for the file amlllm_openai_server-0.1.1-cp312-cp312-manylinux_2_34_aarch64.whl.
File metadata
- Download URL: amlllm_openai_server-0.1.1-cp312-cp312-manylinux_2_34_aarch64.whl
- Upload date:
- Size: 3.2 MB
- Tags: CPython 3.12, manylinux: glibc 2.34+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
987c0c4c86f74707742dea9f3fe23bbeaa05b9605b54af78230ce7dc83c2ee60
|
|
| MD5 |
056712c499c1c1ccaf739756818d6bb6
|
|
| BLAKE2b-256 |
26cb06aebc49e9c14eccd1a7e55b8d7020fb779a75f3a4427cd81dde4e029210
|
File details
Details for the file amlllm_openai_server-0.1.1-cp311-cp311-manylinux_2_34_x86_64.whl.
File metadata
- Download URL: amlllm_openai_server-0.1.1-cp311-cp311-manylinux_2_34_x86_64.whl
- Upload date:
- Size: 12.6 MB
- Tags: CPython 3.11, manylinux: glibc 2.34+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85b51ac9c6180f06301b9059b774b531aa0f854f62ed7888e9b27310d1090251
|
|
| MD5 |
a5a474430e9101d28e658aa99b707e3b
|
|
| BLAKE2b-256 |
8b46731feb8cb126d57a7920ad85fa4f430b44e36b04bc34615d96fdb12ec54a
|
File details
Details for the file amlllm_openai_server-0.1.1-cp311-cp311-manylinux_2_34_aarch64.whl.
File metadata
- Download URL: amlllm_openai_server-0.1.1-cp311-cp311-manylinux_2_34_aarch64.whl
- Upload date:
- Size: 2.9 MB
- Tags: CPython 3.11, manylinux: glibc 2.34+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
68c4081338725676db623d53b4cfdbf16c1c7a6c3419aa64e9309e682ad326a0
|
|
| MD5 |
9f0ea7fc0b05e0257cdb4d907655ccf7
|
|
| BLAKE2b-256 |
c83f423ad3b41c83875691855af37c1cb187b5e25ac3a9798ae4b04a8dcadfa5
|
File details
Details for the file amlllm_openai_server-0.1.1-cp310-cp310-manylinux_2_34_x86_64.whl.
File metadata
- Download URL: amlllm_openai_server-0.1.1-cp310-cp310-manylinux_2_34_x86_64.whl
- Upload date:
- Size: 12.0 MB
- Tags: CPython 3.10, manylinux: glibc 2.34+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fec81faab1315ab401f7b8e2f5be28eaa4620f0b8c3e854e73011c8e29973454
|
|
| MD5 |
9faf7ae8743ec798cfdf14d513dc57fd
|
|
| BLAKE2b-256 |
933879717c9bd82a27e0051e9d1f86fbb8920b45fbaa41674fd21e35a9c3f458
|