migration: preserve mentor and llm streaming slice

This commit is contained in:
leefer
2026-07-31 04:18:53 +08:00
parent 4bab921d14
commit 2919229c73
26 changed files with 1705 additions and 1222 deletions
@@ -0,0 +1,204 @@
from __future__ import annotations
import ast
import hashlib
import unittest
from pathlib import Path
import llm_stream
import mentor_agent
from backend.features.mentor import agent as canonical_agent
from backend.llm import stream as canonical_stream
APP_ROOT = Path(__file__).resolve().parents[1]
ORIGINAL_ROOT = APP_ROOT.parent
MENTOR_SERVICE_METHODS = {
"mentor_setup",
"save_mentor_preferences",
"mentor_stream",
"mentor_messages",
"clear_mentor_messages",
"_validate_mentor_history",
"_build_mentor_context",
"_mentor_market_matrix",
}
LLM_SERVICE_METHODS = {
"_personal_llm_profile",
"_platform_llm_profile",
"_profile_configured",
"_resolved_llm_profile",
"llm_primary_api_key",
"llm_primary_base_url",
"llm_primary_model",
"llm_fallback_api_key",
"llm_fallback_base_url",
"llm_fallback_model",
"llm_source",
"llm_configured",
"llm_fallback_configured",
"save_llm_settings",
"save_llm_mode",
"test_llm_profile",
"_validate_llm_profile",
"llm_access_status",
"_platform_usage_today",
"_platform_usage_today_for_user",
"test_system_llm_profile",
}
MENTOR_REPOSITORY_METHODS = {
"save_mentor_exchange",
"list_mentor_messages",
"delete_mentor_messages",
"list_mentor_preferences",
"save_mentor_preferences",
}
LLM_REPOSITORY_METHODS = {"record_llm_usage", "count_llm_usage_since"}
def sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def class_methods(path: Path, class_name: str) -> dict[str, str]:
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
owner = next(
node
for node in tree.body
if isinstance(node, ast.ClassDef) and node.name == class_name
)
return {
node.name: ast.dump(node, include_attributes=False)
for node in owner.body
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
}
def assignments(path: Path, names: set[str]) -> dict[str, str]:
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
result = {}
for node in tree.body:
if not isinstance(node, ast.Assign) or len(node.targets) != 1:
continue
target = node.targets[0]
if isinstance(target, ast.Name) and target.id in names:
result[target.id] = ast.dump(node.value, include_attributes=False)
return result
class MentorLLMSliceSourceEquivalenceTests(unittest.TestCase):
def assert_methods_equal(
self,
original_path: Path,
original_class: str,
migrated_path: Path,
migrated_class: str,
expected: set[str],
) -> None:
original = class_methods(original_path, original_class)
migrated = class_methods(migrated_path, migrated_class)
self.assertEqual(set(migrated), expected)
for name in sorted(expected):
self.assertEqual(migrated[name], original[name], name)
def test_mentor_agent_and_stream_accumulator_are_exact_files(self) -> None:
self.assertEqual(
sha256(ORIGINAL_ROOT / "mentor_agent.py"),
sha256(APP_ROOT / "backend" / "features" / "mentor" / "agent.py"),
)
self.assertEqual(
sha256(ORIGINAL_ROOT / "llm_stream.py"),
sha256(APP_ROOT / "backend" / "llm" / "stream.py"),
)
def test_compatibility_modules_are_canonical_module_objects(self) -> None:
self.assertIs(mentor_agent, canonical_agent)
self.assertIs(llm_stream, canonical_stream)
def test_mentor_service_methods_are_exact_original_ast(self) -> None:
self.assert_methods_equal(
ORIGINAL_ROOT / "server.py",
"DashboardService",
APP_ROOT / "backend" / "features" / "mentor" / "service.py",
"MentorServiceMixin",
MENTOR_SERVICE_METHODS,
)
def test_llm_service_methods_are_exact_original_ast(self) -> None:
original = class_methods(ORIGINAL_ROOT / "server.py", "DashboardService")
migrated = class_methods(
APP_ROOT / "backend" / "llm" / "service.py", "LLMServiceMixin"
)
self.assertEqual(set(migrated), LLM_SERVICE_METHODS)
adapted = {"_platform_usage_today", "_platform_usage_today_for_user"}
for name in sorted(LLM_SERVICE_METHODS - adapted):
self.assertEqual(migrated[name], original[name], name)
source = (APP_ROOT / "backend" / "llm" / "service.py").read_text(
encoding="utf-8"
)
self.assertIn(
"return self._platform_usage_today_for_user(self.current_user_id)", source
)
self.assertIn("def _platform_usage_today_for_user(self, user_id: int)", source)
def test_mentor_and_llm_repositories_are_exact_original_ast(self) -> None:
self.assert_methods_equal(
ORIGINAL_ROOT / "database.py",
"ReviewDatabase",
APP_ROOT / "backend" / "features" / "mentor" / "repository.py",
"MentorRepositoryMixin",
MENTOR_REPOSITORY_METHODS,
)
self.assert_methods_equal(
ORIGINAL_ROOT / "database.py",
"ReviewDatabase",
APP_ROOT / "backend" / "llm" / "repository.py",
"LLMAuditRepositoryMixin",
LLM_REPOSITORY_METHODS,
)
def test_mentor_data_profiles_are_exact_original_values(self) -> None:
names = {"MENTOR_DATA_PROFILES", "MENTOR_INDEX_UNIVERSE", "MENTOR_ETF_UNIVERSE"}
self.assertEqual(
assignments(ORIGINAL_ROOT / "server.py", names),
assignments(
APP_ROOT / "backend" / "features" / "mentor" / "service.py",
names,
),
)
def test_original_classes_no_longer_duplicate_moved_methods(self) -> None:
remaining_service = class_methods(
APP_ROOT / "backend" / "application.py", "DashboardService"
)
remaining_database = class_methods(APP_ROOT / "database.py", "ReviewDatabase")
remaining_http = class_methods(
APP_ROOT / "backend" / "application.py", "RequestHandler"
)
self.assertTrue(MENTOR_SERVICE_METHODS.isdisjoint(remaining_service))
self.assertTrue(LLM_SERVICE_METHODS.isdisjoint(remaining_service))
self.assertTrue(MENTOR_REPOSITORY_METHODS.isdisjoint(remaining_database))
self.assertTrue(LLM_REPOSITORY_METHODS.isdisjoint(remaining_database))
self.assertTrue(
{"stream_mentor_chat", "save_llm_settings", "save_llm_mode", "test_llm_settings"}
.isdisjoint(remaining_http)
)
def test_http_mixins_preserve_stream_and_model_endpoints(self) -> None:
mentor_http = class_methods(
APP_ROOT / "backend" / "features" / "mentor" / "http.py",
"MentorHttpMixin",
)
llm_http = class_methods(APP_ROOT / "backend" / "llm" / "http.py", "LLMHttpMixin")
self.assertEqual(set(mentor_http), {"stream_mentor_chat"})
self.assertEqual(
set(llm_http), {"save_llm_settings", "save_llm_mode", "test_llm_settings"}
)
if __name__ == "__main__":
unittest.main()