Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-agents/src/lexigram/ai/agents/strategies/token_utils.py: 31%

29 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 07:19 +0800

1"""Shared token accounting helpers for agent strategies. 

2 

3Every strategy funnels LLM completions through these helpers so token 

4usage is counted consistently. Semantics mirror the original 

5``function_calling`` implementation: prefer the prompt/completion split 

6when either side is reported, fall back to ``usage.total_tokens``, and 

7return ``0`` when usage is missing entirely. 

8""" 

9 

10from __future__ import annotations 

11 

12from lexigram.contracts.ai.llm import Completion, CompletionProtocol 

13 

14CompletionInput = Completion | CompletionProtocol 

15 

16 

17def token_split(completion: CompletionInput) -> tuple[int, int]: 

18 """Extract the prompt/completion token split from a completion. 

19 

20 Args: 

21 completion: LLM completion result. 

22 

23 Returns: 

24 Tuple of ``(prompt_tokens, completion_tokens)``. Both are 

25 ``0`` when usage is missing or not reported. 

26 """ 

27 usage = getattr(completion, "usage", None) 

28 if not usage: 

29 return 0, 0 

30 if isinstance(usage, dict): 

31 return ( 

32 int(usage.get("prompt_tokens", 0) or 0), 

33 int(usage.get("completion_tokens", 0) or 0), 

34 ) 

35 return ( 

36 int(getattr(usage, "prompt_tokens", 0) or 0), 

37 int(getattr(usage, "completion_tokens", 0) or 0), 

38 ) 

39 

40 

41def count_tokens(completion: CompletionInput) -> int: 

42 """Count total tokens for a completion. 

43 

44 Prefers the prompt/completion split when either side is reported; 

45 otherwise falls back to ``usage.total_tokens``. Returns ``0`` when 

46 usage is missing entirely. 

47 

48 Args: 

49 completion: LLM completion result. 

50 

51 Returns: 

52 Total token count consumed by the completion. 

53 """ 

54 prompt, completion_tokens = token_split(completion) 

55 if prompt or completion_tokens: 

56 return prompt + completion_tokens 

57 usage = getattr(completion, "usage", None) 

58 if isinstance(usage, dict): 

59 return int(usage.get("total_tokens", 0) or 0) 

60 return int(getattr(usage, "total_tokens", 0) or 0) 

61 

62 

63class TokenAccumulator: 

64 """Mutable token totals accumulated across multiple LLM calls.""" 

65 

66 def __init__(self) -> None: 

67 self.prompt_tokens = 0 

68 self.completion_tokens = 0 

69 self.total_tokens = 0 

70 

71 def add(self, completion: CompletionInput) -> None: 

72 """Accumulate usage from one completion. 

73 

74 Args: 

75 completion: LLM completion result. 

76 """ 

77 prompt, completion_tokens = token_split(completion) 

78 self.prompt_tokens += prompt 

79 self.completion_tokens += completion_tokens 

80 self.total_tokens += count_tokens(completion) 

81 

82 

83__all__ = ["TokenAccumulator", "count_tokens", "token_split"]