-
Notifications
You must be signed in to change notification settings - Fork 0
fix: resolve issue #20 - LLMRequest double-wrap + restore model config + skill toggle sync #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ae59da6
93ca9bb
af5d105
389b9b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,17 +54,30 @@ class MemoryExtractor: | |
| def __init__( | ||
| self, | ||
| tree_store: TomlTreeStore, | ||
| llm_client=None, | ||
| extraction_client=None, | ||
| reflection_client=None, | ||
| *, | ||
| llm_client=None, # Backward compatibility | ||
|
Comment on lines
+57
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Existing callers can pass the former Useful? React with 👍 / 👎. |
||
| llm_chat_timeout: float = _DEFAULT_LLM_CHAT_TIMEOUT, | ||
| ): | ||
| self.tree_store = tree_store | ||
| self.index: MemoryIndex = tree_store.index | ||
| # 每条 LLM 调用的超时;超过即被当成失败、走空提取兜底(不会丢 chunks, | ||
| # 上游的 hippocampus_process 会 re-buffer 回 pending 等下次触发)。 | ||
| self.llm_chat_timeout: float = float(llm_chat_timeout) | ||
| self._llm_client = llm_client | ||
| self._fast_llm_client = None # 轻量模型,用于去重/合并等低复杂度任务 | ||
|
|
||
| # Backward compatibility: the pre-split signature was | ||
| # `MemoryExtractor(tree_store, llm_client)`, where one client served both | ||
| # roles. A lone second argument (positional or via the llm_client kwarg) | ||
| # keeps that meaning — otherwise reflection would silently go unwired. | ||
| if llm_client is not None: | ||
| extraction_client = extraction_client or llm_client | ||
| reflection_client = reflection_client or llm_client | ||
| elif extraction_client is not None and reflection_client is None: | ||
| reflection_client = extraction_client | ||
|
|
||
| self._extraction_client = extraction_client # for extract_*, _check_conflict, merge_facts | ||
| self._reflection_client = reflection_client # for generate_reflections, profile compact (future) | ||
|
Comment on lines
+79
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
|
|
||
| # 升维阈值:facts 积累达到此数量时触发反思 | ||
| self.reflection_threshold = 5 | ||
|
|
@@ -83,17 +96,22 @@ def _truncate_conversation(cls, text: str) -> str: | |
| # 避免模型把开头不完整的句子当成完整事实。 | ||
| return "[…earlier conversation truncated…]\n" + text[-cls.MAX_CONVERSATION_CHARS:] | ||
|
|
||
| def set_llm_client(self, llm_client): | ||
| self._llm_client = llm_client | ||
| def set_extraction_client(self, client): | ||
| """Set the LLM client for extraction, dedup, and merge operations.""" | ||
| self._extraction_client = client | ||
|
|
||
| def set_reflection_client(self, client): | ||
| """Set the LLM client for reflection generation and profile compaction.""" | ||
| self._reflection_client = client | ||
|
|
||
| def set_fast_llm_client(self, fast_llm_client): | ||
| """设置轻量 LLM 客户端,用于去重/合并(回退到 _llm_client)""" | ||
| self._fast_llm_client = fast_llm_client | ||
| def set_llm_client(self, client): | ||
| """Backward compatibility: set both extraction and reflection to the same client.""" | ||
| self._extraction_client = client | ||
| self._reflection_client = client | ||
|
|
||
| @property | ||
| def _fast_or_default(self): | ||
| """获取快速 LLM 客户端,未设置则回退到主 LLM""" | ||
| return self._fast_llm_client or self._llm_client | ||
| def set_fast_llm_client(self, client): | ||
| """Backward compatibility: set extraction client (fast operations).""" | ||
| self._extraction_client = client | ||
|
|
||
| # ========================================== | ||
| # 事实提取(双路径) | ||
|
|
@@ -109,7 +127,7 @@ async def extract_personal_facts(self, conversation_text: str) -> list[dict]: | |
| [{"content": "...", "importance": 7, "tags": [...], | ||
| "speaker_id": "12345", "subject": "昵称", "semantic_id": "..."}, ...] | ||
| """ | ||
| if not self._llm_client: | ||
| if not self._extraction_client: | ||
| return [] | ||
| conversation_text = self._truncate_conversation(conversation_text) | ||
|
|
||
|
|
@@ -140,7 +158,7 @@ async def extract_personal_facts(self, conversation_text: str) -> list[dict]: | |
|
|
||
| try: | ||
| resp = await asyncio.wait_for( | ||
| chat_text(self._llm_client, prompt), | ||
| chat_text(self._extraction_client, prompt), | ||
| timeout=self.llm_chat_timeout | ||
| ) | ||
| if resp: | ||
|
|
@@ -162,7 +180,7 @@ async def extract_group_facts(self, conversation_text: str) -> list[dict]: | |
| [{"content": "...", "importance": 7, "tags": [...], | ||
| "subject": "group", "semantic_id": "..."}, ...] | ||
| """ | ||
| if not self._llm_client: | ||
| if not self._extraction_client: | ||
| return [] | ||
| conversation_text = self._truncate_conversation(conversation_text) | ||
|
|
||
|
|
@@ -194,7 +212,7 @@ async def extract_group_facts(self, conversation_text: str) -> list[dict]: | |
|
|
||
| try: | ||
| resp = await asyncio.wait_for( | ||
| chat_text(self._llm_client, prompt), | ||
| chat_text(self._extraction_client, prompt), | ||
| timeout=self.llm_chat_timeout | ||
| ) | ||
| if resp: | ||
|
|
@@ -211,7 +229,7 @@ async def extract_facts(self, conversation_text: str) -> list[dict]: | |
|
|
||
| 私聊场景只有一个用户,不需要双路径,走单次提取即可。 | ||
| """ | ||
| if not self._llm_client: | ||
| if not self._extraction_client: | ||
| return [] | ||
| conversation_text = self._truncate_conversation(conversation_text) | ||
|
|
||
|
|
@@ -235,7 +253,7 @@ async def extract_facts(self, conversation_text: str) -> list[dict]: | |
|
|
||
| try: | ||
| resp = await asyncio.wait_for( | ||
| chat_text(self._llm_client, prompt), | ||
| chat_text(self._extraction_client, prompt), | ||
| timeout=self.llm_chat_timeout | ||
| ) | ||
| if resp: | ||
|
|
@@ -267,7 +285,7 @@ async def extract_self_awareness( | |
| Returns: | ||
| 觉察文本列表(通常 0-2 条,大部分情况为空) | ||
| """ | ||
| if not self._llm_client: | ||
| if not self._extraction_client: | ||
| return [] | ||
| conversation_text = self._truncate_conversation(conversation_text) | ||
|
|
||
|
|
@@ -297,7 +315,7 @@ async def extract_self_awareness( | |
|
|
||
| try: | ||
| resp = await asyncio.wait_for( | ||
| chat_text(self._llm_client, prompt), | ||
| chat_text(self._extraction_client, prompt), | ||
| timeout=self.llm_chat_timeout | ||
| ) | ||
| if resp: | ||
|
|
@@ -338,7 +356,7 @@ async def generate_semantic_id(self, content: str) -> str: | |
|
|
||
| 回退策略:文本前缀 + hash | ||
| """ | ||
| if not self._llm_client: | ||
| if not self._extraction_client: | ||
| return "" | ||
|
|
||
| prompt = f"""为以下记忆内容生成一个简短的 snake_case 文件名标识符(英文,无空格,不超过 30 字符)。 | ||
|
|
@@ -350,7 +368,7 @@ async def generate_semantic_id(self, content: str) -> str: | |
|
|
||
| try: | ||
| resp = await asyncio.wait_for( | ||
| chat_text(self._llm_client, prompt), | ||
| chat_text(self._extraction_client, prompt), | ||
| timeout=self.llm_chat_timeout | ||
| ) | ||
| if resp: | ||
|
|
@@ -419,7 +437,7 @@ async def deduplicate( | |
|
|
||
| async def _check_conflict(self, new_content: str, existing_content: str) -> str: | ||
| """用 LLM 判断新旧记忆的关系(使用快速模型)""" | ||
| client = self._fast_or_default | ||
| client = self._extraction_client | ||
| if not client: | ||
| return "new" | ||
|
|
||
|
|
@@ -454,7 +472,7 @@ async def _check_conflict(self, new_content: str, existing_content: str) -> str: | |
|
|
||
| async def merge_facts(self, existing_text: str, new_text: str) -> str: | ||
| """LLM 合并两条事实为一条(使用快速模型)""" | ||
| client = self._fast_or_default | ||
| client = self._extraction_client | ||
| if not client: | ||
| return f"{existing_text};{new_text}" | ||
|
|
||
|
|
@@ -567,7 +585,7 @@ async def generate_reflections( | |
| Returns: | ||
| 生成的 reflection 文本列表 | ||
| """ | ||
| if not self._llm_client: | ||
| if not self._reflection_client: | ||
| return [] | ||
|
|
||
| facts = await self.tree_store.get_all_memories( | ||
|
|
@@ -609,7 +627,7 @@ async def generate_reflections( | |
| generated = [] | ||
| try: | ||
| resp = await asyncio.wait_for( | ||
| chat_text(self._llm_client, prompt), | ||
| chat_text(self._reflection_client, prompt), | ||
| timeout=self.llm_chat_timeout | ||
| ) | ||
| if not resp: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the host has a working default LLM but no configured default-fast client, this leaves
extraction_clientasNonewhile still wiring the reflection client.MemoryManager._hippocampus_process()then re-buffers every batch because extraction is mandatory, so automatic fact extraction never runs. The previous adapter explicitly fell back from a missing fast client to the normal default client; preserve that fallback here as well.Useful? React with 👍 / 👎.