OpenAI-compatible proxy for AcademicAI.
It exposes AcademicAI models on a local OpenAI-style API (default: http://127.0.0.1:11435/v1).
- Chat completions: ✅
- Model list endpoint: ✅
- Health endpoint: ✅
- Tool-call emulation (JSON-mode): ✅
- SSE-style streaming emulation: ✅
- Skill snippets on vector basis: 🔴
- TailoredAI RAG interface (per API docs, short re-check): 🔴
- Cost/usage monitoring via API cost endpoint: 🔴
AcademicAI does not provide native OpenAI function-calling/tool-calling in the same way OpenAI-compatible clients expect. This proxy emulates the tool flow so orchestrators (e.g. OpenClaw) can still run tools reliably.
Tool-calling is simulated, not native. That means the model is guided via prompt + JSON parsing, not by a backend-level function-calling engine.
In practice this works well, but there are limits:
- behavior is probabilistic (occasionally the model may answer in JSON style instead of ideal natural text)
- extra guardrails are needed to avoid unnecessary repeated tool calls
- reliability is generally lower than true native tool-calling APIs
So: good for practical use, but not mathematically deterministic.
GET /healthGET /v1/modelsPOST /v1/chat/completions
Client -> Proxy (Bearer):
Authorization: Bearer <YOUR_PROXY_API_KEY>
Proxy -> AcademicAI backend:
X-Client-ID: <ACADEMICAI_CLIENT_ID>X-Client-Secret: <ACADEMICAI_CLIENT_SECRET>
Configure these values in .env (never commit real secrets).
Keep repository content generic. Put tenant-specific values outside the repository:
.envwith endpoint, client ID/secret, proxy API key- optional tenant snippets file via
ACADEMICAI_SKILL_SNIPPETS_FILE
Templates are provided under docs/tenant-template/.
pip install -r requirements.txt
cp .env.example .env
# edit .envRun:
py server.py
# or
.\start_server.ps1This repository is structured so both humans and LLM agents can install it reliably. Recommended approach: give your coding agent the GitHub URL and ask it to perform setup + verification.
Suggested instruction you can paste to an agent:
Install and verify this repository as a local service:
1) clone repo
2) create .env from .env.example
3) fill ACADEMICAI_BASE_URL, ACADEMICAI_CLIENT_ID, ACADEMICAI_CLIENT_SECRET, ACADEMICAI_PROXY_API_KEY
4) install dependencies
5) start server
6) verify /health and /v1/models with Bearer auth
7) run pytest
8) report final status and exact local run command
Minimum verification checks:
GET /healthreturns{"status":"ok"...}GET /v1/modelsworks withAuthorization: Bearer <ACADEMICAI_PROXY_API_KEY>py -m pytest -qpasses
Required:
modelmessages
Forwarded when set:
streamtemperaturemax_tokensmax_completion_tokensfrequency_penaltypresence_penaltyreasoning_effortverbosityseedstopresponse_format(dict only; tool-mode forces{ "type": "json_object" })extra_body.tailoredAiId
Tool emulation input (not forwarded natively):
tools(alias:functions)tool_choice
These defaults apply only when the client did not set the field explicitly.
ACADEMICAI_DEFAULT_CHAT_TEMPERATURE=0.6ACADEMICAI_DEFAULT_CHAT_VERBOSITY=medium(gpt-5* only)ACADEMICAI_DEFAULT_TOOL_TEMPERATURE=0.1ACADEMICAI_DEFAULT_TOOL_VERBOSITY=low(gpt-5* only)ACADEMICAI_DEFAULT_TOOL_REASONING_EFFORT=low(gpt-5* only)
Rule:
- Human conversation without tool mode -> chat defaults
- Tool mode (
tools/functionspresent) -> tool defaults - Explicit client fields always win
You can enable a second LLM pass that rewrites structured/tool-derived output into natural human text.
- Active only for human channels
- Active only in tool mode
- Skipped when the model emits an actual tool call (
finish_reason: tool_calls)
Env flags:
ACADEMICAI_ENABLE_HUMANIZATION_PASS=true|falseACADEMICAI_HUMANIZATION_MODEL=<optional override>(default: same model)ACADEMICAI_HUMANIZATION_TEMPERATURE=0.2
You can enable retrieval-based skill snippets that are injected as short system context before tool-emulation. This increases the chance of correct tool use for domain intents (e.g. mailbox/email -> Himalaya wrapper commands).
Env flags:
ACADEMICAI_ENABLE_SKILL_SNIPPETS=true|falseACADEMICAI_SKILL_SNIPPETS_FILE=./data/skill_snippets.jsonACADEMICAI_SKILL_SNIPPETS_MAX=1
Setup note:
./data/skill_snippets.jsonis installation-specific runtime data (especially if auto-learning is enabled) and is not committed.- To bootstrap, copy
./skill_snippets.example.json→./data/skill_snippets.json(or pointACADEMICAI_SKILL_SNIPPETS_FILEto your own location).
Notes:
- Injection runs only in tool mode (
tools/functionspresent). - Snippets are selected by topic match against the latest user message.
The proxy can auto-update the runtime snippets file (default: ./data/skill_snippets.json) from successful tool-call decisions.
This is intentionally simple (no vector DB / no embeddings):
- derive keywords from the latest user request
- upsert
auto:<tool_name>snippets - increase hit counters and extend topics over time
Env flags:
ACADEMICAI_ENABLE_AUTO_SKILL_LEARNING=true|falseACADEMICAI_AUTO_SKILL_TOPICS_PER_CALL=6ACADEMICAI_AUTO_SKILL_MIN_TOPIC_LEN=4
Notes:
- Learning runs only when tool mode is active and a tool call was actually emitted.
- Existing manual snippets are preserved; auto snippets are marked with
source: "auto".
Cost monitoring is disabled by default and does nothing unless explicitly enabled.
No automatic /api/v1/cost calls are made when disabled.
Env flags:
ACADEMICAI_ENABLE_COST_MONITORING=true|false(default:false)ACADEMICAI_COST_CACHE_FILE=./cost_cache.jsonACADEMICAI_COST_CACHE_TTL_SECONDS=600ACADEMICAI_COST_REFRESH_TIMEOUT_SECONDS=8
Behavior when enabled:
- Proxy lazily/background-refreshes cost cache from AcademicAI
GET /api/v1/cost. - Adds response headers on chat completions (
X-AcademicAI-Total-Cost,X-AcademicAI-Total-Clients,X-AcademicAI-Cost-Entries,X-AcademicAI-Cost-Updated-At,X-AcademicAI-Cost-Stale). - Exposes
GET /internal/cost-status.
Important prerequisites (per AcademicAI API docs):
- API client permission
ACCESS_API_MONITOR_CREDITis required for/api/v1/cost. - Without that permission, the endpoint returns
403and cache stays empty/stale.
If you want proxy defaults to control style, do not hard-set these in OpenClaw for this provider:
temperatureverbosityreasoning_effort
- Proxy reads
toolsfrom request - Injects tool schema/instructions into prompt
- Forces JSON response mode
- Parses model JSON into either:
- single tool call:
{"action":"tool_call",...} - multi-step tool calls:
{"action":"tool_calls","calls":[...]} - normal assistant text (
{"action":"respond",...})
- single tool call:
- Converts tool call(s) into OpenAI
tool_callsresponse (finish_reason: tool_calls) - Upstream orchestrator executes tool and sends
role=toolfollow-up
When tool mode is active and the latest message already has role=tool,
the proxy injects a short guard instruction that prefers a final user-facing
answer and discourages unnecessary additional tool calls.
This reduces accidental re-tooling loops while still allowing another tool call if the latest tool result is clearly incomplete.
For mailbox workflows, the proxy enforces this batch rule:
execcalls containingmessage deleteor anymessage move ...are allowed only if a priorwriteoreditcall exists in the same tool-call batch.
If such a delete/move is blocked and no safe tool call remains, the proxy returns plain text:
Blocked unsafe mail action: message delete/move requires a prior write/edit in the same tool-call batch.
Run smoke + functional tests:
py -m pytest -q
py tests/test_tool_emulation.py
py -m pytest -q tests/test_post_tool_guard.pyOptional:
py tests/test_openclaw_style.pyacademicai-proxy/
academicai/
__init__.py
auth.py
errors.py
provider.py
tool_emulation.py
transformation.py
server.py
start_server.ps1
tests/
test_tool_emulation.py
...
requirements.txt
README.md
If your agent or team uses this proxy in production and you patch a bug, please contribute it upstream so others benefit too:
(If the repository URL changes, update this section accordingly.)
MIT (see LICENSE).