Problem
chat_service.py is 563 lines and has accumulated 49 commits - nearly 2x the next highest-churn file in the repo. It owns at least 7 distinct responsibilities:
- Query classification (
_classify_query)
- Query decomposition / sub-query splitting (
_get_sub_queries)
- Tool call routing (
_get_agent_tool_calls)
- Tool execution dispatch (
_execute_search_tools)
- Context retrieval and assembly (
retrieve_context)
- Relevance scoring and reformulation loop (
_get_query_context_relevance)
- Answer generation (
generate_answer, get_chatbot_reply, get_chatbot_reply_stream)
It also re-defines CODE_BLOCK_PLACEHOLDER_PATTERN (line 36) which is already in api/tools/utils.py (line 17) - a leftover from when some logic was extracted but the constant was copy-pasted instead of imported.
Impact
- Every feature PR touches this file, causing merge conflicts across contributors
- Testing individual responsibilities requires mocking half the module
- The file is the single biggest contributor to the bus factor problem - one person has authored most of the 49 commits
- New contributors avoid touching it because a change anywhere can break everything
Proposed Decomposition
Split into focused modules under api/services/:
| New Module |
Responsibilities Moved |
query_classifier.py |
_classify_query, _get_sub_queries |
tool_dispatcher.py |
_get_agent_tool_calls, _execute_search_tools |
context_retriever.py |
retrieve_context, _get_query_context_relevance, reformulation loop |
chat_service.py (slim) |
get_chatbot_reply, get_chatbot_reply_stream, generate_answer - pure orchestration |
Move CODE_BLOCK_PLACEHOLDER_PATTERN to a shared api/constants.py and import everywhere.
Migration Strategy
This can be done incrementally - one module at a time:
- Extract
query_classifier.py first (smallest, fewest dependencies)
- Update imports in
chat_service.py to use the extracted module
- Run existing tests - they should pass without changes since the public API stays the same
- Repeat for
tool_dispatcher.py then context_retriever.py
Acceptance Criteria
References
Problem
chat_service.pyis 563 lines and has accumulated 49 commits - nearly 2x the next highest-churn file in the repo. It owns at least 7 distinct responsibilities:_classify_query)_get_sub_queries)_get_agent_tool_calls)_execute_search_tools)retrieve_context)_get_query_context_relevance)generate_answer,get_chatbot_reply,get_chatbot_reply_stream)It also re-defines
CODE_BLOCK_PLACEHOLDER_PATTERN(line 36) which is already inapi/tools/utils.py(line 17) - a leftover from when some logic was extracted but the constant was copy-pasted instead of imported.Impact
Proposed Decomposition
Split into focused modules under
api/services/:query_classifier.py_classify_query,_get_sub_queriestool_dispatcher.py_get_agent_tool_calls,_execute_search_toolscontext_retriever.pyretrieve_context,_get_query_context_relevance, reformulation loopchat_service.py(slim)get_chatbot_reply,get_chatbot_reply_stream,generate_answer- pure orchestrationMove
CODE_BLOCK_PLACEHOLDER_PATTERNto a sharedapi/constants.pyand import everywhere.Migration Strategy
This can be done incrementally - one module at a time:
query_classifier.pyfirst (smallest, fewest dependencies)chat_service.pyto use the extracted moduletool_dispatcher.pythencontext_retriever.pyAcceptance Criteria
chat_service.pyreduced to < 200 lines of orchestration logicCODE_BLOCK_PLACEHOLDER_PATTERNdefined in one place, imported everywheretests/unit/services/test_chat_service.pypass without modificationfrom api.services.chat_service import get_chatbot_reply)References
chatbot-core/api/services/chat_service.py(563 lines, 49 commits)chatbot-core/api/tools/utils.pyline 17 (duplicateCODE_BLOCK_PLACEHOLDER_PATTERN)