Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions llm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from dataclasses import dataclass


@dataclass(frozen=True)
class LLMUsage:
prompt_tokens: int
completion_tokens: int
total_tokens: int
reasoning_tokens: int | None = None
cost: float | None = None


@dataclass(frozen=True)
class LLMResponse:
content: str
finish_reason: str | None
usage: LLMUsage | None = None


__all__ = ["LLMResponse", "LLMUsage"]
48 changes: 31 additions & 17 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from openai import OpenAI, OpenAIError

from llm import LLMResponse, LLMUsage
from settings import load_settings
from utils import read_input
from utils import print_usage, read_input


def trim_history(messages, keep_count):
Expand Down Expand Up @@ -108,31 +109,44 @@ def main():
print(f"\nRequest failed: {error}")
continue

answer = "".join(answer)
response_usage = None

if finish_reason == "length":
print("Warning: the answer may be incomplete.")
if usage is not None:

if settings.show_usage and usage:
print("\n-----")
print("Prompt tokens:", usage.prompt_tokens)
print("Completion tokens:", usage.completion_tokens)
print("Total tokens:", usage.total_tokens)
completion_details = getattr(
usage,
"completion_tokens_details",
None,
)

reasoning_tokens = (
usage.completion_tokens_details.reasoning_tokens
if usage.completion_tokens_details
else None
response_usage = LLMUsage(
prompt_tokens=usage.prompt_tokens,
completion_tokens=usage.completion_tokens,
total_tokens=usage.total_tokens,
reasoning_tokens=getattr(
completion_details,
"reasoning_tokens",
None,
),
cost=getattr(usage, "cost", None),
)

print("Reasoning tokens:", reasoning_tokens)
print("Cost:", getattr(usage, "cost", None))
print("-----\n")
response = LLMResponse(
content="".join(answer),
finish_reason=finish_reason,
usage=response_usage,
)

if response.finish_reason == "length":
print("Warning: the answer may be incomplete.")

if settings.show_usage and response.usage:
print_usage(response.usage)

messages.append(
{
"role": "assistant",
"content": answer,
"content": response.content,
}
)
messages = trim_history(messages, settings.memory_keep_count)
Expand Down
40 changes: 40 additions & 0 deletions tests/test_llm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import unittest
from dataclasses import FrozenInstanceError

from llm import LLMResponse, LLMUsage


class LLMDataClassTests(unittest.TestCase):
def test_response_contains_content_finish_reason_and_usage(self):
usage = LLMUsage(
prompt_tokens=10,
completion_tokens=5,
total_tokens=15,
reasoning_tokens=2,
cost=0.001,
)

response = LLMResponse(
content="Hello",
finish_reason="stop",
usage=usage,
)

self.assertEqual(response.content, "Hello")
self.assertEqual(response.finish_reason, "stop")
self.assertEqual(response.usage, usage)

def test_usage_is_optional(self):
response = LLMResponse(content="Hello", finish_reason="stop")

self.assertIsNone(response.usage)

def test_response_is_immutable(self):
response = LLMResponse(content="Hello", finish_reason="stop")

with self.assertRaises(FrozenInstanceError):
response.content = "Changed"


if __name__ == "__main__":
unittest.main()
12 changes: 11 additions & 1 deletion utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,14 @@ def read_input(prompt):
return None


__all__ = ["read_input"]
def print_usage(usage):
print("\n-----")
print(f"Prompt tokens: {usage.prompt_tokens}")
print(f"Completion tokens: {usage.completion_tokens}")
print(f"Reasoning tokens: {usage.reasoning_tokens}")
print(f"Total tokens: {usage.total_tokens}")
print(f"Cost: {usage.cost}")
print("-----\n")


__all__ = ["print_usage", "read_input"]