Bug Description
Summary
nanobot serve exposes an OpenAI-compatible /v1/chat/completions endpoint, but every successful response includes hardcoded zero token usage:
"usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0 }
The agent loop already tracks real usage from provider responses in AgentLoop._last_usage, but the HTTP API layer does not forward it. This breaks any downstream billing/metering integration that relies on the standard OpenAI usage field.
Root cause
In nanobot/api/server.py, _chat_completion_response() hardcodes usage to zero:
"usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0},
handle_chat_completions() calls agent_loop.process_direct(...) and only uses the returned text. It never reads usage from the agent loop afterward.
Meanwhile, AgentLoop already stores provider usage after each run:
# nanobot/agent/loop.py
self._last_usage: dict[str, int] = {}
...
self._last_usage = result.usage
Other parts of nanobot already consume _last_usage (for example /status via nanobot/command/builtin.py), so the data exists internally but is not exposed via the HTTP API.
Suggested fix
After process_direct() completes (non-streaming path), read agent_loop._last_usage
Pass it into _chat_completion_response() (or merge into the final JSON)
Optionally include usage in the streaming path as well (e.g. final SSE chunk or a documented extension)
Add/adjust tests in tests/test_openai_api.py to assert non-zero usage when the mock agent sets _last_usage
Document real usage behavior in docs/openai-api.md
Example sketch:
def _chat_completion_response(content: str, model: str, usage: dict[str, int] | None = None) -> dict[str, Any]:
usage = usage or {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
return {
...
"usage": {
"prompt_tokens": int(usage.get("prompt_tokens", 0)),
"completion_tokens": int(usage.get("completion_tokens", 0)),
"total_tokens": int(usage.get("total_tokens", 0)),
},
}
And in handle_chat_completions() after a successful non-streaming call:
usage = getattr(agent_loop, "_last_usage", None) or {}
return web.json_response(_chat_completion_response(response_text, model_name, usage=usage))
Impact / use case
We run nanobot instances behind a router that bills users per token (OpenAI-style metering). Because usage is always zero, billing integrations cannot deduct credits or write usage logs unless they patch nanobot locally or estimate tokens heuristically.
Any external system using nanobot serve as an OpenAI-compatible proxy for cost tracking, quotas, or analytics is affected.
Environment
Repo: HKUDS/nanobot (main as of June 2026)
Command: nanobot serve
Endpoint: POST /v1/chat/completions (stream=false)
Related docs: docs/openai-api.md (does not currently mention real usage reporting)
Additional context
OpenAI-compatible API was added in #1362
Internal token tracking exists elsewhere (e.g. /status, JSONL usage recorder), but not in the serve HTTP response
Current API tests validate response shape and session behavior, but do not require real usage values
---
## Labels (if you have triage access)
Suggested: `bug`, `api`, `good first issue` (small, localized fix)
---
## Note on assignment
On public repos you usually **cannot assign** issues to the project unless you are a maintainer or they use an org workflow. Filing the issue on HKUDS/nanobot is enough; maintainers will triage it.
If you want a shorter title for mobile notifications:
> **Serve API returns zero usage despite agent tracking real tokens**
### Steps to Reproduce
Configure a working LLM provider in config.json
Start the API server:
nanobot serve
Send a non-streaming request:
curl -s http://127.0.0.1:8900/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"hi"}],"stream":false}' \
| jq '.usage'
Actual behavior
{
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0
}
This happens even when the assistant reply is non-empty and the underlying LLM call succeeds.
### Expected Behavior
Expected behavior
The response should include real token counts from the completed agent run, matching OpenAI-compatible semantics:
{
"prompt_tokens": <int>,
"completion_tokens": <int>,
"total_tokens": <int>
}
If usage is temporarily unavailable, documenting that behavior would also be helpful; silently returning zeros is misleading for API consumers.
### Relevant Logs
```shell
nanobot Version
v0.2.1
Python Version
3.12
Operating System
Linux
Channel / Platform
Telegram
LLM Provider
OpenAI
Configuration (Optional)
Additional Context
No response
Bug Description
Summary
nanobot serveexposes an OpenAI-compatible/v1/chat/completionsendpoint, but every successful response includes hardcoded zero token usage:nanobot Version
v0.2.1
Python Version
3.12
Operating System
Linux
Channel / Platform
Telegram
LLM Provider
OpenAI
Configuration (Optional)
Additional Context
No response