Files
xiaoli-cli/core/conversation.py
T
Xiaoli Dev e22652fbbb feat: 增强编码能力 - 新增代码编辑器、代码搜索、Git集成
- 新增 plugins/code_editor.py: 精准代码编辑(搜索替换、批量编辑、diff对比)
- 新增 plugins/code_search.py: 代码搜索与理解(跨文件搜索、符号提取、依赖分析)
- 新增 plugins/git_tools.py: Git 版本控制(status/diff/log/commit/branch)
- 新增 core/ 模块: 配置管理、引擎管理、对话处理、显示输出、代码沙箱
- 修复 requirements.txt: 使用正确的 pip 包名(Pillow、opencv-python 等)
- 更新 unified_tool_manager.py: 兼容 Plugin 和 Liugin 两种类名
- 更新系统提示词: 增强编程工作流指导

本次更新由 mimo 自动化执行,与开发者无关。
2026-04-25 10:55:27 +08:00

316 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
对话处理模块 - 核心对话循环和工具调用
"""
import json
import re
import time
import threading
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
from colorama import Fore, Style
logger = logging.getLogger(__name__)
class ConversationManager:
"""对话管理器 - 处理用户输入、AI 响应、工具调用循环"""
def __init__(self, cli):
self.cli = cli
self.history = []
self.max_history = 999999
def process(self, user_input: str):
"""处理完整的对话循环"""
self.history.append({"role": "user", "content": user_input})
tool_prompts = self._get_tool_prompts()
loop_count = 0
current_input = user_input
max_loops = 20
while loop_count < max_loops:
response = self._generate_with_animation(current_input, tool_prompts)
processed = self._process_thinking(response)
processed = self._process_code_blocks(processed)
try:
text_content, json_data = self._parse_response(processed)
if json_data:
if text_content:
self._add_and_display(text_content)
if isinstance(json_data, dict):
result = self._handle_dict(json_data, text_content, processed)
if result == "break":
break
elif result == "continue":
loop_count += 1
continue
elif isinstance(json_data, list):
result = self._handle_list(json_data, text_content, processed)
if result == "break":
break
elif result == "continue":
loop_count += 1
continue
else:
display = text_content if text_content else processed
self._add_and_display(display)
break
except json.JSONDecodeError:
text_content, json_data = self._parse_response(processed)
if json_data:
# Re-process with parsed data
pass
else:
self._add_and_display(processed)
break
if loop_count >= max_loops:
self.cli.display.output(f"AI 已达到最大循环次数 ({max_loops}),已停止")
def _handle_dict(self, data, text_content, processed):
"""处理 dict 类型的 JSON 数据"""
if data.get('action') == 'use_tool':
self.cli._execute_single_tool(data)
return "break"
if data.get('action') == 'continue':
content = data.get('content', '')
if content:
self._add_and_display(content)
return "continue"
if data.get('continue') or data.get('need_continue') or data.get('think_more'):
msg = data.get('message', '')
if msg:
self.history.append({"role": "assistant", "content": msg})
self.cli.display.response(msg, is_continue=True)
return "continue"
msg = data.get('message', '')
if msg:
self._add_and_display(msg)
return "break"
if text_content:
self._add_and_display(text_content)
else:
self._add_and_display(processed)
return "break"
def _handle_list(self, data_list, text_content, processed):
"""处理 list 类型的 JSON 数据"""
tool_calls = [item for item in data_list
if isinstance(item, dict) and item.get('action') == 'use_tool']
if tool_calls:
self.cli._execute_concurrent_tools(tool_calls)
return "break"
continue_items = [item for item in data_list
if isinstance(item, dict) and
(item.get('continue') or item.get('need_continue') or item.get('think_more'))]
if continue_items:
msg = continue_items[0].get('message', '')
if msg:
self.history.append({"role": "assistant", "content": msg})
self.cli.display.response(msg, is_continue=True)
return "continue"
if text_content:
self._add_and_display(text_content)
else:
self._add_and_display(processed)
return "break"
def _add_and_display(self, content):
"""添加到历史并显示"""
self.history.append({"role": "assistant", "content": content})
self.cli.display.response(content)
def _generate_with_animation(self, user_input, tool_prompts=None):
"""生成 AI 响应(带动画)"""
system_prompt = self._build_system_prompt(tool_prompts)
if not self.cli.engine_manager.current_engine:
return "错误: 当前没有可用的AI引擎"
# TUI 模式:直接生成
if self.cli.display.tui_callback:
response = self.cli.engine_manager.generate_response(user_input, system_prompt)
if response:
self.history.append({"role": "assistant", "content": response})
return response
# CLI 模式:带动画
stop_event = threading.Event()
esc_event = threading.Event()
# 加载等待句子
sentences = self._load_love_sentences()
anim_thread = threading.Thread(
target=self.cli.display.thinking_animation,
args=(sentences, stop_event, esc_event), daemon=True)
anim_thread.start()
try:
response = self.cli.engine_manager.generate_response(user_input, system_prompt)
if response:
self.cli.display.typeprint(response, Fore.CYAN)
self.history.append({"role": "assistant", "content": response})
return response
finally:
stop_event.set()
anim_thread.join()
def _build_system_prompt(self, tool_prompts=None):
"""构建系统提示词"""
base = ("你叫小狸,是一个强大的智能编程助手。你具备精准的代码编辑能力、"
"完整的 Git 工作流支持、以及跨文件代码搜索和理解能力。"
"你可以帮助用户编写、修改、调试代码,管理版本控制,理解复杂代码库。")
tool_section = ""
if tool_prompts:
tool_section = f"\n\n{tool_prompts}"
call_format = """
【工具调用格式】
当需要执行操作时,在回复中包含 JSON 指令:
1. 纯 JSON: {"action": "use_tool", "tool": "工具名", "args": "参数"}
2. 文本+JSON: 好的,我来修改这个文件。{"action": "use_tool", "tool": "code_editor", "args": "edit file.py old_code <<<>>> new_code"}
【核心工具】
- code_editor: 精准代码编辑(edit/search/diff/insert/create/write)
- code_search: 代码搜索与理解(find/regex/symbols/imports/structure)
- git_tools: Git 操作(status/diff/log/add/commit/branch)
- cmd_executor: 执行 shell 命令
- file_manager: 文件管理
【编码工作流】
1. 先用 code_search 了解代码结构
2. 用 code_editor.read_range 查看相关代码
3. 用 code_editor.edit 精准修改
4. 用 git_tools 提交变更
【注意事项】
- code_editor.edit 的格式: old_text <<<>>> new_text(用 <<<>>> 分隔)
- args 必须是字符串
- 可以一次返回多个 JSON 指令顺序执行
- 使用 continue 补充回答: {"action": "continue", "content": "补充内容"}"""
return base + tool_section + call_format
def _process_thinking(self, response):
"""处理深度思考标记"""
# <think/> 格式
if '<think/>' in response:
parts = response.split('<think/>', 1)
if len(parts) >= 2:
thinking = parts[0].strip()
reply = parts[1].strip()
if thinking:
print(f"{Fore.LIGHTBLACK_EX}[思考: {thinking[:200]}...]{Style.RESET_ALL}" if len(thinking) > 200 else f"{Fore.LIGHTBLACK_EX}[思考: {thinking}]{Style.RESET_ALL}")
return reply if reply else ""
# <thinking>...</thinking> 格式
engine = self.cli.engine_manager.current_engine
if engine:
start = getattr(engine, 'thinking_start_marker', None)
end = getattr(engine, 'thinking_end_marker', None)
if start and end and start in response and end in response:
parts = response.split(start, 1)
if len(parts) > 1 and end in parts[1]:
thinking, reply = parts[1].split(end, 1)
if thinking.strip():
print(f"{Fore.LIGHTBLACK_EX}[思考: {thinking.strip()[:200]}]{Style.RESET_ALL}")
return reply.strip()
return response
def _process_code_blocks(self, text):
"""处理代码块标记"""
pattern = r'(""".*?""")'
def replace(m):
inner = m.group(1)[3:-3]
return f'{Fore.RED}{inner}{Style.RESET_ALL}'
return re.sub(pattern, replace, text, flags=re.DOTALL)
def _parse_response(self, response):
"""解析混合响应(文本+JSON)"""
response = response.strip()
# 直接 JSON
try:
parsed = json.loads(response)
if isinstance(parsed, (dict, list)):
return "", parsed
except json.JSONDecodeError:
pass
# 代码块中的 JSON
pattern = r"```(?:json)?\s*({.*?})\s*```"
matches = re.findall(pattern, response, re.DOTALL)
if matches:
try:
data = json.loads(matches[-1])
if isinstance(data, (dict, list)):
text = re.sub(pattern, "", response, flags=re.DOTALL).strip()
return text, data
except json.JSONDecodeError:
pass
# 内联 JSON
markers = ['{"action": "use_tool"', '{"action": "continue"',
'{"action":"use_tool"', '{"action":"continue"']
for marker in markers:
idx = response.find(marker)
if idx != -1:
brace_count = 0
for i in range(idx, len(response)):
if response[i] == '{':
brace_count += 1
elif response[i] == '}':
brace_count -= 1
if brace_count == 0:
try:
data = json.loads(response[idx:i+1])
if isinstance(data, dict) and data.get('action') in ['use_tool', 'continue']:
return response[:idx].strip(), data
except json.JSONDecodeError:
pass
break
return response, None
def _get_tool_prompts(self):
"""获取工具提示词"""
if not self.cli.tool_manager or not self.cli.tool_manager.tools:
return "当前没有可用的工具"
lines = ["可用工具列表:\n"]
for tool in self.cli.tool_manager.tools:
name = tool.get('name', '')
desc = tool.get('description', '')
lines.append(f"- {name}: {desc}")
return '\n'.join(lines)
def _load_love_sentences(self):
"""加载等待动画句子"""
import os
path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "love.txt")
if os.path.exists(path):
try:
with open(path, 'r', encoding='utf-8') as f:
return [l.strip() for l in f.readlines() if l.strip()]
except:
pass
return ["AI正在思考中...", "请稍等片刻...", "正在处理您的请求..."]