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
16 changes: 1 addition & 15 deletions bot/agent.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import asyncio
import collections
import logging
import os
from typing import Any
Expand All @@ -25,7 +24,6 @@
)

MAX_TURNS = 25
MAX_CHATS = 200
MCP_SESSION_TIMEOUT_SECONDS = 30.0


Expand Down Expand Up @@ -73,31 +71,19 @@ def __init__(
mcp_servers=(mcp_servers if mcp_servers is not None else []),
)
self.name = name
self._conversations: collections.OrderedDict[str, list[TResponseInputItem]] = (
collections.OrderedDict()
)
self._conversations: dict[str, list[TResponseInputItem]] = {}
self._locks: dict[str, asyncio.Lock] = {}

def _evict_oldest(self) -> None:
"""Remove the least-recently-used chat when over MAX_CHATS."""
while len(self._conversations) > MAX_CHATS:
evicted_id, _ = self._conversations.popitem(last=False)
self._locks.pop(evicted_id, None)

def get_messages(self, chat_id: str) -> list[TResponseInputItem]:
return self._conversations.get(chat_id, [])

def set_messages(self, chat_id: str, messages: list[TResponseInputItem]) -> None:
self._conversations[chat_id] = messages
self._conversations.move_to_end(chat_id)
self._evict_oldest()

def append_user_message(self, chat_id: str, message: str) -> None:
if chat_id not in self._conversations:
self._conversations[chat_id] = []
self._conversations[chat_id].append({"role": "user", "content": message})
self._conversations.move_to_end(chat_id)
self._evict_oldest()

def truncate_history(self, chat_id: str) -> None:
"""Keep only the last MAX_TURNS turns of conversation history.
Expand Down
70 changes: 0 additions & 70 deletions tests/test_agent.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations

import asyncio
from unittest.mock import create_autospec
from unittest.mock import patch

Expand All @@ -9,7 +8,6 @@
from openai import AsyncAzureOpenAI

from bot.agent import DEFAULT_INSTRUCTIONS
from bot.agent import MAX_CHATS
from bot.agent import MAX_TURNS
from bot.agent import OpenAIAgent

Expand Down Expand Up @@ -239,71 +237,3 @@ def test_empty_mcp_servers(self):
config = {"mcpServers": {}}
agent = OpenAIAgent.from_dict("test", config)
assert agent.agent.mcp_servers == []


class TestChatEviction:
def test_default_max_chats(self):
assert MAX_CHATS == 200

def test_evicts_oldest_chat_when_limit_exceeded(self, monkeypatch):
monkeypatch.setattr("bot.agent.MAX_CHATS", 3)
agent = OpenAIAgent(name="test")

agent.set_messages("C001", [{"role": "user", "content": "a"}])
agent.set_messages("C002", [{"role": "user", "content": "b"}])
agent.set_messages("C003", [{"role": "user", "content": "c"}])
assert len(agent._conversations) == 3

agent.set_messages("C004", [{"role": "user", "content": "d"}])
assert "C001" not in agent._conversations
assert len(agent._conversations) == 3
assert set(agent._conversations.keys()) == {"C002", "C003", "C004"}

def test_updating_existing_chat_does_not_evict(self, monkeypatch):
monkeypatch.setattr("bot.agent.MAX_CHATS", 2)
agent = OpenAIAgent(name="test")

agent.set_messages("C001", [{"role": "user", "content": "a"}])
agent.set_messages("C002", [{"role": "user", "content": "b"}])
agent.set_messages("C001", [{"role": "user", "content": "updated"}])
assert len(agent._conversations) == 2
assert agent.get_messages("C001")[0]["content"] == "updated"

def test_append_to_new_chat_triggers_eviction(self, monkeypatch):
monkeypatch.setattr("bot.agent.MAX_CHATS", 2)
agent = OpenAIAgent(name="test")

agent.set_messages("C001", [{"role": "user", "content": "a"}])
agent.set_messages("C002", [{"role": "user", "content": "b"}])
agent.append_user_message("C003", "c")

assert "C001" not in agent._conversations
assert len(agent._conversations) == 2

def test_accessing_chat_refreshes_its_position(self, monkeypatch):
monkeypatch.setattr("bot.agent.MAX_CHATS", 3)
agent = OpenAIAgent(name="test")

agent.set_messages("C001", [{"role": "user", "content": "a"}])
agent.set_messages("C002", [{"role": "user", "content": "b"}])
agent.set_messages("C003", [{"role": "user", "content": "c"}])

# Refresh C001
agent.set_messages("C001", [{"role": "user", "content": "refreshed"}])

# C002 is now oldest — adding C004 should evict C002
agent.set_messages("C004", [{"role": "user", "content": "d"}])
assert "C002" not in agent._conversations
assert "C001" in agent._conversations

def test_eviction_also_cleans_up_lock(self, monkeypatch):
monkeypatch.setattr("bot.agent.MAX_CHATS", 2)
agent = OpenAIAgent(name="test")

agent._locks["C001"] = asyncio.Lock()
agent.set_messages("C001", [{"role": "user", "content": "a"}])
agent._locks["C002"] = asyncio.Lock()
agent.set_messages("C002", [{"role": "user", "content": "b"}])

agent.set_messages("C003", [{"role": "user", "content": "c"}])
assert "C001" not in agent._locks
Loading