装好 Ollama 之后,用几行 Python 就能搭一个本地代码问答助手。支持多轮对话、代码高亮、项目上下文理解,完全离线,数据不上传。
你有没有这种经历:写代码卡住了,想问 AI 解决办法,打开 ChatGPT 网页版,复制粘贴,等回复,中途还想换个模型……折腾半天,思路也断了。
更烦的是,有些代码涉及到公司内网项目,不能随便粘贴到公共 AI 工具里。
今天做一个本地代码问答助手。装好 Ollama,写一个 Python 脚本,就能在终端里直接问编程问题。全程离线,数据不出机器。
项目背景
编程助手已经不是什么新鲜事了。Cursor、Copilot 这些工具很火,但都有一个问题:代码要上传到第三方服务器。
对于个人开发者来说,用免费模型就够了。对于企业来说,代码安全才是第一位的。
本地部署一个代码问答助手,满足两个需求:
- 代码问题随手问,不用切换窗口
- 敏感项目代码不出本机
这个项目用 Python + Ollama,在终端里运行,支持多轮对话,能理解你的代码上下文。
技术选型
| 组件 | 选择 | 理由 |
|---|---|---|
| 大模型 | Ollama + qwen2.5:7b | 中文能力强,代码生成效果好 |
| 交互方式 | 终端 CLI | 零配置,直接跑 |
| 上下文管理 | 内置对话历史 | 支持多轮追问 |
| 代码高亮 | rich 库 | 终端里也能看高亮代码 |
| 项目文件读取 | os + glob | 自动读取当前目录代码 |
Ollama 安装(还没装的话):
curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen2.5:7b
qwen2.5:7b 在 8GB 内存的机器上能跑,显存够的话可以换 14b 版本。
实现步骤
第一步:安装依赖
pip install ollama rich
就两个包。ollama 是官方 Python SDK,rich 用来在终端里渲染高亮代码。
第二步:写代码问答助手
新建 code_assistant.py:
#!/usr/bin/env python3
"""本地代码问答助手"""
import ollama
import rich
from rich.console import Console
from rich.markdown import Markdown
from rich.syntax import Syntax
import glob
import os
console = Console()
def read_project_context(project_dir="."):
"""读取当前目录下的代码文件,作为上下文"""
contexts = []
extensions = ["*.py", "*.js", "*.ts", "*.go", "*.java", "*.rs", "*.md", "*.json", "*.yaml", "*.yml", "*.sh"]
for ext in extensions:
for filepath in glob.glob(os.path.join(project_dir, "**", ext), recursive=True):
# 排除 node_modules、.git 等目录
if any(skip in filepath for skip in ["node_modules", ".git", "__pycache__", ".venv", "venv"]):
continue
try:
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
contexts.append(f"文件: {filepath}\n```\n{content}\n```")
except Exception:
continue
return "\n\n".join(contexts[:50000]) # 限制上下文长度
def get_user_question():
"""获取用户提问"""
console.print("\n[bold cyan]💡 你可以问我任何编程问题,输入 /exit 退出,/clear 清空历史[/bold cyan]")
return console.input("[bold green]你: [/bold green]")
def chat(question, conversation_history, project_context):
"""发送问题到 Ollama,返回回答"""
system_prompt = """你是一个专业的编程助手。你的任务是帮助开发者解决编程问题。
规则:
1. 用中文回答
2. 代码片段要用 markdown 代码块包裹,并标注语言
3. 解释要简洁明了,不要啰嗦
4. 如果问题涉及具体项目代码,结合上下文回答
5. 不确定时坦诚说明,不要编造"""
# 构建消息
messages = [{"role": "system", "content": system_prompt}]
# 添加项目上下文(首次对话时)
if conversation_history == [] and project_context:
messages.append({
"role": "system",
"content": f"以下是当前项目的部分代码上下文,供参考:\n{project_context}"
})
# 添加对话历史
messages.extend(conversation_history)
messages.append({"role": "user", "content": question})
# 调用 Ollama
response = ollama.chat(model="qwen2.5:7b", messages=messages)
answer = response["message"]["content"]
return answer
def main():
console.print("[bold blue]🤖 本地代码问答助手[/bold blue]")
console.print("[dim]基于 Ollama + qwen2.5,完全离线运行[/dim]\n")
# 读取项目上下文
console.print("[dim]📂 正在读取项目代码上下文...[/dim]")
project_context = read_project_context(".")
if project_context:
console.print(f"[dim]✅ 已加载项目上下文,覆盖 {project_context.count('文件:')} 个文件[/dim]")
else:
console.print("[dim]⚠️ 未找到代码文件,将以通用模式运行[/dim]")
conversation_history = []
while True:
question = get_user_question().strip()
if not question:
continue
# 处理特殊命令
if question.lower() == "/exit" or question.lower() == "/quit":
console.print("[dim]👋 再见![/dim]")
break
if question.lower() == "/clear":
conversation_history = []
console.print("[dim]🗑️ 对话历史已清空[/dim]")
continue
if question.lower() == "/context":
console.print(f"[dim]📊 项目上下文长度: {len(project_context)} 字符[/dim]")
continue
# 显示思考中
console.print("[dim]🤔 思考中...[/dim]")
try:
answer = chat(question, conversation_history, project_context)
# 更新对话历史
conversation_history.append({"role": "user", "content": question})
conversation_history.append({"role": "assistant", "content": answer})
# 控制历史长度,避免上下文过长
if len(conversation_history) > 20:
conversation_history = conversation_history[-20:]
# 渲染回答
console.print("\n[bold magenta]🤖 助手:[/bold magenta]")
console.print(Markdown(answer))
except Exception as e:
console.print(f"[red]❌ 错误: {e}[/red]")
console.print("[dim]请检查 Ollama 是否正在运行: ollama list[/dim]")
if __name__ == "__main__":
main()
第三步:运行
python code_assistant.py
第一次运行会连接本地 Ollama 服务,调用 qwen2.5:7b 模型。如果模型还没下载,ollama 会自动拉取。
运行效果
🤖 本地代码问答助手
基于 Ollama + qwen2.5,完全离线运行
📂 正在读取项目代码上下文...
✅ 已加载项目上下文,覆盖 12 个文件
💡 你可以问我任何编程问题,输入 /exit 退出,/clear 清空历史
你: Python 里怎么实现一个 LRU 缓存?
🤔 思考中...
🤖 助手:
Python 3.2+ 内置了 `functools.lru_cache`,可以直接用:
```python
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
如果需要自定义实现,可以用 OrderedDict:
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity: int):
self.cache = OrderedDict()
self.capacity = capacity
def get(self, key: str) -> int:
if key not in self.cache:
return -1
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key: str, value: int) -> None:
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False)
你: OrderedDict 为什么比 dict 更适合做 LRU?
🤔 思考中…
🤖 助手:
因为 OrderedDict 保持了插入顺序,并且提供了 move_to_end() 方法:
- 普通 dict(Python 3.7+)虽然也保持插入顺序,但没有移动元素到末尾的方法
move_to_end(key)可以把访问过的元素移到最后,这样最旧的元素就在最前面popitem(last=False)可以删除最旧的元素(队首)
这两个操作结合起来,就能高效实现 LRU 的"访问即置顶、满员淘汰最旧"逻辑。
### 常用命令
| 命令 | 作用 |
|------|------|
| `/exit` | 退出程序 |
| `/clear` | 清空对话历史 |
| `/context` | 查看当前加载的项目上下文 |
## 扩展方向
这个项目已经能用了,但还可以继续打磨:
1. **支持更多模型** — 加上 `ollama list` 自动检测可用模型,让用户切换
2. **文件级上下文** — 当前是读整个项目,可以改成只读当前编辑的文件
3. **Web 界面** — 用 Gradio 或 Streamlit 包一层,手机上也能用
4. **记忆功能** — 把对话历史存到 SQLite,重启后还能接着聊
5. **代码执行** — 接一个沙箱环境,直接跑代码看结果
6. **插件系统** — 支持自定义 prompt 模板,针对不同语言优化
## 小结
装好 Ollama,写一个 Python 脚本,十几分钟就能搭一个本地代码问答助手。
不用联网,不用花钱,代码不上传。问完问题关掉终端,什么痕迹都不留。
你的代码安全,你自己说了算。
要不要试试?装个 Ollama,跑一下这个脚本,问问你最近在头疼的代码问题。
这篇文章对你有帮助吗?