Skip to content

feat:add_request_level_token_callback#1362

Open
fengju0213 wants to merge 14 commits into
mainfrom
update_token_logic
Open

feat:add_request_level_token_callback#1362
fengju0213 wants to merge 14 commits into
mainfrom
update_token_logic

Conversation

@fengju0213

Copy link
Copy Markdown
Collaborator

Related Issue

Closes #https://github.com/eigent-ai/eigent_server/issues/46

Description

Summary

This PR adapts Eigent to CAMEL request-level usage reporting for non-stream
mode by introducing a dedicated SSE event request_usage, enabling real-time
token accumulation during a step loop.

Background

Previously, Eigent consumed token usage mainly via deactivate_agent (step-
level). This is too coarse for long multi-request loops inside a single step
and can delay downstream billing/accounting actions.

What Changed

  • Added new backend action/event:
    • Action.request_usage
    • typed payload ActionRequestUsageData
  • Wired CAMEL callback in ListenChatAgent:
    • auto-detects whether CAMEL ChatAgent supports on_request_usage
    • when available, emits request_usage per model request with:
      • tokens (request total tokens)
      • request_index
      • response_id
      • step_total_tokens
      • agent/task identity fields
  • Added SSE forwarding:
    • chat_service now emits request_usage
  • Frontend adaptation:
    • added AgentStep.REQUEST_USAGE
    • chatStore consumes request_usage and calls addTokens(...)
      immediately
    • updated agent message type fields for new payload keys
  • Added dedup logic for non-stream mode:
    • when request-level reporting is enabled, non-stream
      deactivate_agent.tokens is set to 0
    • prevents double counting (request_usage + deactivate_agent)
  • Added unit test:
    • verifies request_usage increments tokens in real time
    • verifies subsequent deactivate_agent(tokens=0) does not over-count

Compatibility

  • If CAMEL version does not expose on_request_usage, Eigent falls back to
    previous step-level behavior automatically.
  • Streaming behavior remains unchanged.

Validation

  • python3 -m py_compile backend/app/agent/listen_chat_agent.py backend/app/ service/task.py backend/app/service/chat_service.py
  • npm run test -- test/unit/store/chatStore.test.ts
  • Result: passed

Scope

  • Non-stream request-level token reporting only.
  • No token-by-token streaming settlement in this PR.

Testing Evidence (REQUIRED)

  • I have included human-verified testing evidence in this PR.
  • This PR includes frontend/UI changes, and I attached screenshot(s) or screen recording(s).
  • No frontend/UI changes in this PR.

What is the purpose of this pull request?

  • Bug fix
  • New Feature
  • Documentation update
  • Other

Contribution Guidelines Acknowledgement

@fengju0213

fengju0213 commented Feb 24, 2026

Copy link
Copy Markdown
Collaborator Author
Screen.Recording.2026-02-24.at.14.47.55.mp4

The video shows that the token is updated with each tool call (request).

@fengju0213

Copy link
Copy Markdown
Collaborator Author

pending camel-ai/camel#3872

@Pakchoioioi Pakchoioioi requested review from Wendong-Fan and bytecii and removed request for Wendong-Fan and bytecii February 26, 2026 12:03
Comment thread backend/app/agent/listen_chat_agent.py

@Wendong-Fan Wendong-Fan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

seems in streaming mode _stream_chunks / _astream_chunks call _send_agent_deactivate(accumulated_content, total_tokens) in their finally block with the full step token count, while request_usage events have already reported those same tokens, the frontend adds both, resulting in inflated token counts?

added pr #1439

@Douglasymlai Douglasymlai added enhancement New feature or request backend labels Jul 3, 2026
@Zephyroam Zephyroam self-requested a review July 6, 2026 11:08
Zephyroam and others added 3 commits July 6, 2026 18:13
Resolve conflict in listen_chat_agent.py: combine main's model-reload /
auth-retry logic with the request-level token callback.

- Move api_task_id/agent_name assignment before super().__init__ (needed by
  the _on_request_usage callback) while keeping main's _model_reload_callback
  / _model_reload_lock setup after super().
- Keep both _on_request_usage (request-level usage) and the auth-retry
  reload methods from main.
- _send_agent_deactivate zeroes tokens when CAMEL exposes on_request_usage,
  so streaming (_stream_chunks/_astream_chunks) no longer double-counts.
- Consolidate astep's inline deactivate to call _send_agent_deactivate,
  matching sync step() and removing the duplicated zeroing branch.
- Bump camel-ai to main's 0.2.91a3 (still exposes on_request_usage).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…zeroed

Request-level token reporting relies on CAMEL's on_request_usage callback,
but OpenAI-family providers omit usage from streamed responses unless
`stream_options.include_usage` is set. Since the step-level deactivate is
zeroed once request-level reporting is active, streaming steps (the
task_agent runs streaming by default) were counted as 0 tokens.

Inject `stream_options.include_usage=True` in agent_model when streaming,
except for native-SDK platforms (Anthropic, Bedrock, etc.) that reject the
OpenAI-specific param and report streaming usage on their own.

Verified against a real model (openai-compatible, streaming):
- before: request_usage fires with 0 tokens -> step counted 0
- after:  request_usage tokens=366, deactivate_agent tokens=0 (exact, no double count)
- anthropic task_agent: stream=True, no stream_options injected (unchanged)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Once request-level usage reporting is active, _send_agent_deactivate zeroes
deactivate_agent.tokens. Two consumers relied on that per-event field:

- single-agent SSE: _action_to_sse did not forward Action.request_usage, so
  in single-agent mode request-level events were consumed and dropped while
  the deactivate fallback was zeroed -> no real-time token updates (only the
  final end.tokens backfilled the total). Forward request_usage like
  chat_service does.
- question_confirm quick-reply detection (chatStore): read the zeroed
  deactivate_agent.tokens, so hasTokens was always false and the quick-reply
  "mark trigger execution Completed" branch never ran. Base it on the task's
  accumulated total (getTokens) instead.

Verified:
- _action_to_sse(ActionRequestUsageData(tokens=123)) now emits
  `data: {"step": "request_usage", ...}` (was None/dropped)
- backend tests: test_listen_chat_agent (15 passed, 1 skipped),
  test_single_agent_service (1 passed)
- frontend chatStore tests unchanged (5 pre-existing getProjectById
  failures, request_usage case passes)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

4 participants