尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从零构建终端AI编程助手:基于大语言模型的Coding Agent实现

从零构建终端AI编程助手:基于大语言模型的Coding Agent实现 1. 项目概述为什么要在终端里“手搓”一个Coding Agent如果你是一个重度命令行用户每天花在终端里的时间比在图形界面里还多那你可能和我有一样的“执念”为什么所有酷炫的AI编程助手比如GitHub Copilot、Cursor都得绑定在一个特定的编辑器或IDE里为什么不能在终端这个我最熟悉、最高效的“主战场”里直接让AI帮我写代码、改bug、解释命令这个想法就是我启动ChCode这个项目的初衷——一个完全运行在终端里的、由Python手搓的Coding Agent。ChCode不是一个简单的脚本包装器。它是我用Python近7000行代码从零开始构建的一个本地优先的智能编程伙伴。它的核心目标很明确让你无需离开终端就能获得接近甚至超越现代AI IDE的编码辅助体验。无论是写一个快速的Python脚本调试一段复杂的Shell命令还是理解一个陌生的代码库你只需要在终端里敲几个命令ChCode就能理解你的意图并给出可执行的代码、清晰的解释或直接的修改。这背后涉及的技术栈相当“硬核”你需要处理自然语言理解让AI明白你的模糊需求、代码生成与补全、上下文管理记住我们刚才在讨论什么、与本地开发环境的深度集成读取文件、运行测试、以及一个稳定可靠的终端用户界面TUI。市面上没有现成的方案所以只能自己动手。接下来我会详细拆解ChCode的设计思路、核心实现、以及那些只有踩过坑才知道的实操细节。2. 核心架构设计如何让AI在终端里“思考”和“动手”构建一个终端Coding Agent远不止是调用一下OpenAI的API那么简单。它需要一套完整的架构来协调AI的“大脑”和终端的“手脚”。ChCode的架构可以概括为“一个核心循环四大功能模块”。2.1 智能体Agent的核心工作流ChCode的核心是一个事件驱动的工作流我称之为“感知-思考-行动”循环。这个循环是Agent智能的体现。感知PerceptionAgent持续监听终端输入。这不仅仅是等待用户输入命令还包括监控当前工作目录的文件变化、读取特定文件的内容、甚至捕获上一条命令的执行输出作为上下文。例如当你运行python script.py报错后直接对ChCode说“修复这个错误”它能自动将报错信息作为上下文。思考Cognition这是AI大模型发挥作用的地方。Agent将“感知”到的所有信息用户指令、当前文件、终端历史、错误日志整合成一个结构化的提示Prompt发送给后端的大语言模型LLM。这里的Prompt工程至关重要它需要明确告诉模型你的角色是一个终端助手你拥有读取、写入、执行文件的权限你必须以可执行的代码块或明确的终端命令作为回应。行动Action接收到LLM的回复后Agent需要解析并安全地执行。回复可能是一个Shell命令、一段Python代码、或者一个文件修改建议。ChCode内置了一个“动作执行器”它会安全检查对于高危命令如rm -rf /,:(){ :|: };:等会要求用户二次确认。上下文执行如果是Python代码它会在一个隔离的、但能访问当前工作目录变量的子进程中执行确保代码能操作到正确的文件。结果反馈将执行结果成功输出或错误信息再次反馈给用户并作为下一轮“感知”的输入形成一个闭环。注意让AI在终端里拥有“执行权”是双刃剑。在ChCode的设计中任何涉及文件写入、系统修改或网络请求的“行动”默认都处于“建议模式”。即AI会先给出它想执行的命令或代码并询问你是否确认执行。只有在你明确同意后它才会动作。这是一个绝对不能妥协的安全底线。2.2 四大核心模块详解围绕这个核心循环我构建了四个主要模块会话与上下文管理模块这是Agent的“短期记忆”。它维护一个会话Session里面记录了当前对话的历史、涉及的文件路径、以及最近几次命令执行的结果。这个模块的关键在于Token的精打细算。LLM的上下文窗口是有限的比如128K不能无脑地把所有历史记录都塞进去。我的策略是“摘要化”和“优先级”将过去的冗长对话总结成几句话并优先保留与当前任务最相关的代码片段和错误信息。工具调用Tool Calling模块这是Agent的“手”。为了让AI不仅能说还能做我实现了一套工具调用机制。我将常用功能封装成“工具”例如read_file(file_path): 读取文件内容。write_file(file_path, content): 写入文件内容需确认。execute_shell(command): 执行Shell命令。search_in_directory(keyword, path): 在目录中搜索关键词。 LLM在思考时可以选择调用这些工具。当LLM的回复中包含特定的工具调用JSON结构时本模块会拦截回复执行对应工具并将工具执行结果重新喂给LLM让它基于结果继续思考或回答用户。这实现了多步复杂任务比如“帮我找到所有包含‘TODO’的文件并列出它们”。终端用户界面TUI模块这是Agent的“脸”。一个友好的CLI工具不能只有冰冷的文本流。我使用textual或rich这样的库构建了一个简单的TUI。它通常分为几个区域一个主聊天区域显示对话历史一个输入框用于键入指令一个状态栏显示当前模型、Token消耗等信息有时还有一个侧边栏显示当前会话涉及的文件树。TUI模块要处理好异步刷新确保AI生成内容时界面不会卡死。大模型后端抽象层这是Agent的“大脑”供应商。为了不被任何一家AI服务商绑定我设计了一个抽象层。无论是OpenAI的GPT系列、Anthropic的Claude还是本地的Ollama运行Llama 3、CodeLlama等甚至是同时使用多个模型都通过统一的接口进行调用。这只需要在配置文件中改一个参数比如从openai/gpt-4切换到ollama/codellama:7b。# 模型后端抽象层的简化示例 class ModelBackend: def __init__(self, config): self.config config if config[provider] openai: self.client OpenAIClient(config[api_key]) elif config[provider] ollama: self.client OllamaClient(config[base_url]) # ... 其他提供商 async def generate(self, messages, toolsNone): 统一生成接口 if self.config[provider] openai: return await self._generate_openai(messages, tools) elif self.config[provider] ollama: return await self._generate_ollama(messages) # ... async def _generate_openai(self, messages, tools): # 调用OpenAI API支持工具调用 response await self.client.chat.completions.create( modelself.config[model], messagesmessages, toolstools, # 传递工具定义 tool_choiceauto, ) # 解析响应判断是普通回复还是工具调用 return self._parse_openai_response(response)这个架构确保了ChCode既灵活又强大既能理解复杂需求又能安全地操作本地环境。3. 关键技术实现与踩坑实录纸上谈兵容易真正写起代码来到处都是“坑”。下面我分享几个关键技术的实现细节和那些让我掉了几把头发的教训。3.1 上下文管理的艺术与Token经济学LLM的上下文窗口是宝贵的资源。如何把最相关的信息塞进去是终端Agent体验好坏的关键。我的策略是“分层缓存动态注入”工作区快照Workspace Snapshot当用户开启一个新会话或聚焦于一个新项目时ChCode会快速扫描当前目录生成一个轻量级的文件树摘要例如src/main.py (120 lines), tests/test_main.py (45 lines), requirements.txt并提取README.md或pyproject.toml的关键内容如项目描述、依赖。这个快照在会话初始化时一次性注入帮助模型建立对项目的基本认知。活跃文件上下文用户当前正在编辑或讨论的文件其全部内容会被优先保留在上下文中。这是成本最高但也是价值最高的部分。相关性检索RAG-lite当用户的问题涉及项目其他部分时我实现了一个简单的基于关键词和向量相似度的检索。例如用户问“Database类是怎么处理连接的”我会用这个句子去向量化然后与项目中所有代码片段通过预分割得到的向量计算相似度把最相关的2-3个片段动态插入到上下文里。这里我用了sentence-transformers生成向量本地运行速度可以接受。对话历史压缩随着对话轮数增加历史记录会膨胀。我采用两种方式压缩总结式压缩每5轮对话后让LLM自己将之前的对话总结成一段“背景摘要”。丢弃式压缩采用一个滑动窗口只保留最近N轮对话的原始内容更早的只保留摘要。实操心得不要试图把所有代码都塞给模型。对于大型项目直接塞一个几千行的文件不仅Token爆炸模型效果也会变差。一定要让模型“按需索取”。我的经验是优先保证“用户指令”和“最近一次AI回复”的完整性其次是“活跃文件”最后才是检索到的相关片段。Token预算的分配比例大致可以按 指令:活跃文件:检索内容 1:3:2 来预估。3.2 工具调用的可靠性与错误处理工具调用是Agent能力的倍增器但也是最容易出错的地方。1. 工具描述的精确性给LLM的工具描述必须极其精确和无歧义。例如read_file工具不仅要说明功能还要说明参数file_path是相对路径还是绝对路径我统一用相对于当前工作目录的路径以及文件不存在时的行为抛出异常并告知模型。模糊的描述会导致模型产生错误的调用。2. 解析与验证的鲁棒性LLM返回的Tool Call不一定总是完美的JSON。你需要做多层防御JSON解析容错用json.loads包裹在try...except中如果失败尝试用正则表达式提取可能的结构或者直接反馈给模型“你的工具调用格式有误请重试”。参数验证检查file_path是否在项目根目录内防止越权访问检查命令是否在黑名单中。类型转换LLM返回的数字可能是字符串需要正确转换。3. 执行隔离与超时控制执行Shell命令或Python代码必须在子进程中进行并设置超时。import subprocess import asyncio async def execute_shell(command, timeout30): try: proc await asyncio.create_subprocess_shell( command, stdoutasyncio.subprocess.PIPE, stderrasyncio.subprocess.PIPE ) try: stdout, stderr await asyncio.wait_for(proc.communicate(), timeout) except asyncio.TimeoutError: proc.kill() await proc.wait() return False, , fCommand timed out after {timeout} seconds. return_code proc.returncode return return_code 0, stdout.decode(), stderr.decode() except Exception as e: return False, , fFailed to execute command: {str(e)}这段代码确保了即使命令卡死也不会拖垮主Agent进程。4. 结果反馈的格式化工具执行的结果成功或失败需要以一种清晰、结构化的方式反馈给LLM让它能理解并据此决定下一步。我通常格式化为[TOOL RESULT] Tool: execute_shell Status: Success/Failed Stdout: 命令输出内容 Stderr: 错误输出内容 Return Code: 退出码清晰的反馈能极大提高多步任务的成功率。3.3 终端UI的异步挑战与性能优化在终端里做一个响应式的TUI同时还要跑AI推理和文件IO异步编程是唯一的选择。我选择asyncio作为并发框架。主要的挑战和解决方案阻塞主事件循环任何同步的、耗时的操作如读取大文件、计算向量相似度都不能直接放在UI线程里。我的做法是使用asyncio.to_thread()将同步函数放到线程池中运行或者用aiofiles这样的异步库替代同步文件操作。# 错误做法在异步函数中直接同步读文件 content open(large_file.txt).read() # 这会阻塞 # 正确做法使用异步文件库或线程池 import aiofiles async with aiofiles.open(large_file.txt, r) as f: content await f.read() # 或者 content await asyncio.to_thread(lambda: open(large_file.txt).read())流式输出体验等待LLM生成大段代码时如果一直转圈圈用户体验很差。我实现了流式响应如果后端支持如OpenAI。每当收到一个Token就立即更新UI中的消息气泡。这需要仔细处理UI组件的状态更新避免刷新冲突。内存管理长时间运行的Agent会话会积累大量上下文和历史。我设置了一个内存警戒线当内存占用超过一定阈值会自动触发历史对话的压缩和清理并提醒用户可以考虑重启会话。踩坑记录初期我混用了asyncio和多线程导致了一些难以调试的竞态条件UI偶尔会卡死。后来我严格遵循“所有IO和耗时操作都异步化”的原则并将状态管理集中到几个线程安全的队列中由主事件循环统一处理稳定性大大提升。给你的建议是在TUI项目中尽早确定并坚持一种并发模型不要混用。4. 从安装到实战ChCode的完整使用指南理论说了这么多我们来点实际的。下面是如何从零开始使用ChCode并让它成为你终端利器的完整步骤。4.1 环境准备与安装ChCode是纯Python项目理论上跨平台但在Linux/macOS的终端体验最佳。1. 基础依赖Python 3.10 必须因为用了很多新语法和异步特性pip Python包管理器2. 安装ChCode目前ChCode还没有上PyPI你需要从源码安装。# 1. 克隆仓库 git clone https://your-git-repo.com/username/chcode.git cd chcode # 2. 创建虚拟环境强烈推荐避免污染系统环境 python -m venv .venv # Linux/macOS激活 source .venv/bin/activate # Windows激活 .venv\Scripts\activate # 3. 安装依赖和ChCode本身 pip install -e . # -e 代表可编辑模式方便你后续修改代码 # 或者直接安装核心依赖 pip install openai rich textual sentence-transformers httpx aiofiles3. 配置API密钥或本地模型ChCode需要一个大脑。你需要准备一个后端。选项A使用OpenAI等云端API简单但需付费和网络在~/.config/chcode/config.yaml或项目根目录的config.yaml中配置model: provider: openai name: gpt-4-turbo # 或 gpt-3.5-turbo api_key: sk-... # 你的OpenAI API Key base_url: https://api.openai.com/v1 # 默认如果你用代理或第三方兼容服务可改选项B使用本地Ollama免费离线但对硬件有要求首先确保你安装了 Ollama 并拉取了合适的模型如ollama pull codellama:7b。 然后修改配置model: provider: ollama name: codellama:7b # 你拉取的模型名 base_url: http://localhost:11434 # Ollama默认地址4.2 基础使用与常用命令安装配置好后在终端输入chcode即可启动TUI界面。但ChCode也支持纯CLI模式适合快速任务。1. TUI模式交互式直接运行chcode。你会看到一个分屏界面。在底部的输入框你可以像聊天一样和Agent对话。示例1代码生成输入“写一个Python函数用递归计算斐波那契数列的第n项”。ChCode会生成代码并询问你是否要运行或保存。示例2错误诊断在终端里运行一个命令出错后切换到ChCode TUI输入“我刚运行了python test.py它报错了帮我看看”。ChCode会自动捕获上一条命令的输出如果支持或等你粘贴错误然后分析原因。快捷键CtrlN: 新建会话。CtrlF: 聚焦到文件侧边栏如果有。Tab: 在输入框和聊天区域间切换焦点。CtrlC: 中断AI的生成。CtrlQ: 退出。2. CLI模式单次任务对于一次性任务使用chcode run命令。# 直接让AI执行一个任务 chcode run 为当前目录下的app.py文件写一个单元测试 # 指定使用某个文件作为上下文 chcode run --file app.py 解释这个文件里的main函数做了什么 # 将AI的输出直接保存到文件 chcode run 生成一个fastapi的hello world示例 demo.pyCLI模式非常适合集成到脚本或自动化流程中。4.3 高级技巧让ChCode成为你的超级外脑掌握了基础下面这些技巧能让你和ChCode的协作效率翻倍。1. 利用“”引用和文件上下文在TUI输入时使用符号可以快速引用文件。请优化 utils/helper.py 中的calculate_stats函数。– ChCode会自动读取该文件内容作为上下文。比较 model_v1.py 和 model_v2.py 的主要区别。– ChCode会同时读取两个文件。2. 自定义工具插件ChCode支持你编写自己的Python工具。在配置目录下创建一个tools/文件夹里面放你的工具脚本。# ~/.config/chcode/tools/my_tools.py from chcode.sdk import Tool Tool( nameget_weather, description获取指定城市的当前天气, params{ city: {type: string, description: 城市名称如北京} } ) async def get_weather(city: str): # 这里调用一个天气API # 返回结构化的结果 return {city: city, temperature: 22°C, condition: 晴}重启ChCode后AI就能在需要时调用你的get_weather工具了。你可以把公司内部的API、数据库查询等封装成工具打造专属的超级助手。3. 会话持久化与分享ChCode的会话会自动保存。你可以用chcode session list查看所有历史会话用chcode session load session_id加载某个旧会话继续工作。这对于一个需要多天完成的长任务非常有用。你甚至可以将会话导出为一个文件分享给同事他们加载后就能看到完整的对话历史和上下文。5. 常见问题与故障排除在实际使用中你肯定会遇到一些问题。这里我整理了最常遇到的几个及其解决方法。5.1 启动与连接问题问题现象可能原因解决方案启动时报ModuleNotFoundError依赖未安装完整或虚拟环境未激活。1. 确认已激活虚拟环境。2. 在项目根目录重新运行pip install -e .。启动TUI后一片空白或卡死终端不支持或TUI库与终端不兼容。1. 尝试在更标准的终端中运行如gnome-terminal,kitty,iTerm2。2. 设置环境变量TERMxterm-256color。3. 使用CLI模式chcode run hello测试核心功能。连接模型API失败 (OpenAI)网络问题、API密钥错误、或余额不足。1. 检查网络连接。2. 运行chcode config show确认API密钥配置正确。3. 访问OpenAI后台检查额度。连接Ollama失败Ollama服务未启动或模型未下载。1. 运行ollama serve启动服务。2. 运行ollama list确认模型存在。3. 检查配置中的base_url是否为http://localhost:11434。5.2 使用过程中的异常问题现象可能原因解决方案AI回复“我无法执行此操作”或忽略工具调用Prompt工程问题或工具描述不够清晰。1. 在指令中更明确地要求AI使用工具如“请使用read_file工具查看config.yaml的内容”。2. 检查工具定义的description是否清晰无歧义。AI生成的代码无法运行模型幻觉或缺少必要的上下文。1. 要求AI分步执行先输出计划。2. 提供更详细的错误信息让AI诊断。3. 切换到能力更强的模型如从gpt-3.5升级到gpt-4。处理大项目时响应极慢或内存暴涨上下文过长触发了低效的检索或压缩逻辑。1. 使用chcode session clear-context清理当前会话上下文。2. 在配置中调低context.max_tokens限制。3. 聚焦于特定子目录而不是整个项目根目录。工具执行如文件写入失败权限不足或文件路径不存在。1. 检查当前终端用户对目标目录是否有写权限。2. 让AI使用绝对路径或明确相对路径的基准。5.3 性能优化与模型选择响应慢如果使用云端API网络延迟是主要因素。考虑使用流式响应至少能看到生成过程。如果使用本地模型响应速度取决于你的硬件GPU CPU和模型大小7B模型比13B/70B快。对于终端交互延迟低于5秒是可以接受的超过10秒体验就会很差。我个人的选择是日常快速任务用gpt-3.5-turbo或codellama:7b复杂代码推理用gpt-4或deepseek-coder:33b如果有足够GPU内存。效果不佳如果AI总是答非所问或代码质量差首先检查你的Prompt。终端Agent的Prompt需要明确其身份、能力和约束。其次检查上下文是否提供了足够且准确的信息。最后考虑升级模型。代码任务上专门的代码模型如CodeLlama, DeepSeek-Coder通常比通用模型如ChatGPT表现更好。成本控制使用云端API时Token就是钱。启用context.compress选项积极使用工具调用工具描述不计入上下文Token以及为不同任务选择不同价位的模型简单解释用便宜模型复杂架构用贵模型是控制成本的关键。开发ChCode的过程是一个不断与AI的“不可预测性”和终端环境的“复杂性”作斗争的过程。但看到它最终能流畅地理解“帮我把这个CSV文件的前三列提取出来转换成JSON并计算第二列的平均值”这样的复杂指令并一步步调用工具完成时那种成就感是无与伦比的。它不再是一个玩具而是一个真正能提升终端工作效率的伙伴。这个项目也让我深刻体会到将AI能力无缝融入现有工作流比单纯追求模型的“大”和“新”更有实际价值。如果你也热爱终端不妨基于这些思路打造属于你自己的Coding Agent。
返回列表