feat(cli): enable vector search and improve chat history management

This commit is contained in:
jinli.yl 2026-02-15 13:29:46 +08:00
parent cdda48aab4
commit a8604af78f
7 changed files with 85 additions and 31 deletions

19
docs/make_mp4.md Normal file
View file

@ -0,0 +1,19 @@
```shell
ffmpeg -i /Users/yuli/Desktop/remecli_en.mov \
-vf "scale=-2:1080,setpts=0.333*PTS" \
-c:v libx264 \
-crf 28 \
-preset fast \
-c:a aac \
-b:a 96k \
/Users/yuli/Desktop/remecli_en_1080p_3x.mp4
ffmpeg -i /Users/yuli/Desktop/remecli_zh.mov \
-vf "scale=-2:1080,setpts=0.333*PTS" \
-c:v libx264 \
-crf 28 \
-preset fast \
-c:a aac \
-b:a 96k \
/Users/yuli/Desktop/remecli_zh_1080p_3x.mp4
```

View file

@ -1,5 +1,6 @@
"""FsCli system prompt"""
import asyncio
from datetime import datetime
from pathlib import Path
@ -8,6 +9,7 @@ from loguru import logger
from ...core.enumeration import Role, ChunkEnum
from ...core.op import BaseReactStream
from ...core.schema import Message, StreamChunk
from ...core.utils import format_messages
from ...tool.fs import BashTool, LsTool, ReadTool, WriteTool, EditTool
@ -31,18 +33,23 @@ class FsCli(BaseReactStream):
self.messages: list[Message] = []
self.previous_summary: str = ""
self.summary_tasks: list[asyncio.Task] = []
async def reset(self) -> str:
"""Reset conversation history using summary.
def add_summary_task(self, messages: list[Message]):
"""Add summary task to queue."""
remaining_tasks = []
for task in self.summary_tasks:
if task.done():
exc = task.exception()
if exc is not None:
logger.exception(f"Summary task failed: {exc}")
else:
result = task.result()
logger.info(f"Summary task completed: {result}")
else:
remaining_tasks.append(task)
self.summary_tasks = remaining_tasks
Summarizes current messages to memory files and clears history.
"""
if not self.messages:
self.messages.clear()
self.previous_summary = ""
return "No history to reset."
# Import required modules
from ..fs import FsSummarizer
# Summarize current conversation and save to memory files
@ -59,14 +66,30 @@ class FsCli(BaseReactStream):
language=self.language,
)
result = await summarizer.call(
messages=self.messages,
date=current_date,
service_context=self.service_context,
summary_task = asyncio.create_task(
summarizer.call(
messages=messages,
date=current_date,
service_context=self.service_context,
),
)
self.summary_tasks.append(summary_task)
async def new(self) -> str:
"""Reset conversation history using summary.
Summarizes current messages to memory files and clears history.
"""
if not self.messages:
self.messages.clear()
self.previous_summary = ""
return "No history to reset."
self.add_summary_task(self.messages)
self.messages.clear()
self.previous_summary = ""
return f"History saved to memory files and reset. Result: {result.get('answer', 'Done')}"
return "History saved to memory files and reset."
async def context_check(self) -> dict:
"""Check if messages exceed token limits."""
@ -104,20 +127,16 @@ class FsCli(BaseReactStream):
tokens_before = cut_result.get("token_count", 0)
if force_compact:
# Force compact: summarize all messages, leave only summary
messages_to_summarize = self.messages
turn_prefix_messages = []
left_messages = []
elif not cut_result.get("needs_compaction", False):
# No compaction needed
return "History is within token limits, no compaction needed."
else:
# Normal compaction: use cut point result
messages_to_summarize = cut_result.get("messages_to_summarize", [])
turn_prefix_messages = cut_result.get("turn_prefix_messages", [])
left_messages = cut_result.get("left_messages", [])
# Step 2: Generate summary via Compactor
compactor = FsCompactor(language=self.language)
summary_content = await compactor.call(
messages_to_summarize=messages_to_summarize,
@ -126,14 +145,22 @@ class FsCli(BaseReactStream):
service_context=self.service_context,
)
# Step 3: Call reset_history to save and clear
reset_result = await self.reset()
self.add_summary_task(messages=messages_to_summarize)
# Step 4: Assemble final messages
self.messages = left_messages
self.previous_summary = summary_content
return f"History compacted from {tokens_before} tokens. {reset_result}"
return f"History compacted from {tokens_before} tokens."
def format_history(self) -> str:
"""Format history messages."""
return format_messages(
messages=self.messages,
add_index=False,
add_reasoning=False,
strip_markdown_headers=False,
)
async def build_messages(self) -> list[Message]:
"""Build system prompt message."""

View file

@ -13,7 +13,7 @@ from ...core.utils import format_messages
class FsSummarizer(BaseReact):
"""Retrieve personal memories through vector search and history reading."""
def __init__(self, working_dir: str, memory_dir: str = "memory", version: str = "default", **kwargs):
def __init__(self, working_dir: str, memory_dir: str = "memory", version: str = "v1", **kwargs):
super().__init__(**kwargs)
self.working_dir: str = working_dir
self.memory_dir: str = memory_dir

View file

@ -19,7 +19,7 @@ memory_stores:
store_name: reme
embedding_model: default
fts_enabled: true
vector_enabled: false
vector_enabled: true
file_watchers:
default:

View file

@ -81,4 +81,5 @@ def print_logo(service_config: "ServiceConfig"):
expand=False,
)
Console().print(Group("\n", panel, "\n"), justify="center")
# use justify="center" to adjust position
Console().print(Group("\n", panel, "\n"))

View file

@ -39,8 +39,10 @@ class ReMeCli(ReMeFs):
"/help": "Show help.",
}
async def chat_with_remy(self, tool_result_max_size: int = 100, language: str = "zh", **kwargs):
async def chat_with_remy(self, tool_result_max_size: int = 100, **kwargs):
"""Interactive CLI chat with Remy using simple streaming output."""
language = self.service_config.language
print(f"ReMe language={language}")
tools: list[BaseTool] = [
FsMemorySearch(vector_weight=self.vector_weight, candidate_multiplier=self.candidate_multiplier),
BashTool(cwd=self.working_dir),
@ -53,10 +55,10 @@ class ReMeCli(ReMeFs):
tavily_api_key: str = os.getenv("TAVILY_API_KEY", "")
dashscope_api_key: str = os.getenv("DASHSCOPE_API_KEY", "")
if tavily_api_key:
tools.append(TavilySearch(name="web_search"))
tools.append(TavilySearch(name="web_search", language=language))
print("find tavily_api_key, append Tavily search tool")
elif dashscope_api_key:
tools.append(DashscopeSearch(name="web_search"))
tools.append(DashscopeSearch(name="web_search", language=language))
print("find dashscope_api_key, append Dashscope search tool")
else:
print("No Tavily or Dashscope API key found, skip Tavily and Dashscope search tool")
@ -108,7 +110,7 @@ class ReMeCli(ReMeFs):
break
if user_input == "/new":
result = await fs_cli.reset()
result = await fs_cli.new()
print(f"{result}\nConversation reset\n")
continue
@ -117,6 +119,11 @@ class ReMeCli(ReMeFs):
print(f"{result}\nHistory compacted.\n")
continue
if user_input == "/history":
result = fs_cli.format_history()
print(f"Formated History:\n{result}\n")
continue
if user_input == "/clear":
fs_cli.messages.clear()
print("History cleared.\n")

View file

@ -10,11 +10,11 @@ role_prompt: |
{query}
# task
Extract the original content related to the user's query directly from the context, maintain accuracy, and avoid excessive processing.
Return all the original search results directly without processing.
role_prompt_zh: |
# 用户问题
{query}
# task
直接从上下文中提取与用户问题相关的原始内容,保持准确性,避免过度处理。
直接返回所有的原始搜索结果,不要处理