🚀

用 Python + Ollama 搭一个本地代码问答助手,随时随地问编程问题

装好 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,跑一下这个脚本,问问你最近在头疼的代码问题。

这篇文章对你有帮助吗?