基于Streamlit与Gemini API构建轻量级AI代码生成与对话工具
1. 项目概述:一个基于Gemini的轻量级AI编码伴侣
最近在尝试各种AI编码工具时,我发现了一个挺有意思的痛点:像GitHub Copilot或Cursor这类工具虽然强大,但它们要么是IDE插件,要么是桌面应用,有时候我只是想快速验证一个想法,或者临时需要一段代码,并不想打开一个庞大的开发环境。于是,我动手用Streamlit和Google的Gemini API搭建了一个叫“CursorAISim”的Web应用。本质上,它是一个轻量级的、浏览器即开的AI编码伴侣,核心目标是把“提问-生成代码-讨论优化-管理片段”这个闭环流程,在一个简洁的页面上跑通。
这个工具特别适合几种场景:当你手头没有安装完整IDE,比如在平板或临时用的电脑上;当你需要快速给团队演示一个算法或API调用示例;或者当你学习新语言、新框架,想有个随时能对话、能出代码的“陪练”。它不是一个要替代专业IDE的庞然大物,而更像一个随时待命的“代码便签本”和“即时答疑助手”。整个项目用Python写成,前端是Streamlit,后端逻辑围绕Gemini API构建,并用Pydantic来确保数据流转的清晰和稳定。接下来,我会详细拆解从环境搭建、核心功能实现到实际使用中踩过的坑和优化技巧。
2. 技术栈选型与架构设计思路
2.1 为什么选择Streamlit + Gemini API这个组合?
选择技术栈时,我的核心诉求是“快速验证”和“极低的前端成本”。Streamlit几乎是为数据科学家和机器学习工程师快速构建交互式应用而生的,它用Python脚本就能生成Web界面,省去了前后端分离、部署路由、处理HTTP请求等一系列繁琐工作。对于这样一个工具类应用,它的st.text_input、st.button、st.chat_message等组件完全够用,并且状态管理(st.session_state)和缓存(st.cache_resource)机制能很好地支撑起一个会话式应用的需求。
后端AI模型的选择上,我对比了OpenAI的GPT系列和Google的Gemini。最终选择Gemini API,主要是出于几个考虑:一是其gemini-2.0-flash模型在代码生成和理解任务上表现相当出色,响应速度快,成本也相对友好;二是Google AI Studio提供了直观的API密钥管理和免费额度,对个人开发者和小项目起步非常友好;三是其API设计简洁,Python SDK(google-generativeai)集成起来非常顺畅,几行代码就能完成初始化、配置和调用。
注意:虽然项目示例中使用了
gemini-2.0-flash,但Gemini模型系列更新较快。在实际部署时,建议根据任务复杂度在gemini-2.0-flash(速度优先)、gemini-2.0-pro(复杂推理优先)或更新的预览版模型间做权衡。模型切换通常只需修改一行初始化代码。
2.2 核心数据流与状态管理设计
应用的核心数据流围绕着“用户输入 -> AI处理 -> 结果展示与管理”展开。为了不让代码变成一团乱麻,我引入了Pydantic来强制进行数据建模和验证。这步非常关键,它让应用的“骨架”清晰可见。
我定义了三个核心的Pydantic模型:
CodeSnippet:代表一个代码片段。包含id(唯一标识)、filename(建议的文件名)、language(编程语言)、content(代码内容)和created_at(创建时间戳)。这样,每个生成的代码块都是一个结构化的对象,而不是散落在各处的字符串。ChatMessage:代表聊天记录中的一条消息。包含role(“user”或“assistant”)、content(消息内容)和timestamp。这为聊天历史提供了清晰的格式。AppState:这是应用的“大脑”,一个全局状态容器。它包含chat_history(ChatMessage列表)、code_snippets(CodeSnippet列表)、selected_snippet_ids(用户勾选准备下载的片段ID集合)等。所有动态数据都收纳在这里。
使用st.session_state来持久化这个AppState对象。Streamlit脚本在用户每次交互后都会从头到尾重新执行,session_state是唯一能跨这些“重跑”保持数据的地方。把状态集中管理,避免了状态分散导致的bug,也使得调试和功能扩展(比如未来添加历史持久化)变得容易。
2.3 前端布局与交互逻辑规划
界面布局采用经典的左右分栏结构,这是为了同时满足“代码生产”和“对话讨论”两个核心场景,避免功能互相干扰。
- 左侧栏(Sidebar):放置全局配置和操作。这里最重要的是Gemini API密钥的输入框。出于安全考虑,密钥输入使用
st.text_input的type=“password”模式,并且仅在会话中记忆,不会硬编码或泄露。这里还放置了全局的“清空聊天记录”、“清空代码片段”等管理按钮。 - 主区域(两列布局):
- 左列 - 代码工坊:这里是代码的“生产车间”。顶部通过
st.radio组件让用户选择核心操作模式:“生成代码”、“解释代码”或“通过聊天修正代码”。根据选择,下方动态渲染对应的输入表单(如生成代码的提示词输入框、解释代码的代码粘贴区)。下方是一个可折叠的区域(st.expander),用于展示所有已生成或管理的代码片段(st.code高亮显示),每个片段旁有一个复选框用于选择下载。 - 右列 - 对话间:这里是代码的“研讨室”。构建一个完整的聊天界面,使用
st.chat_message根据角色(用户/AI)渲染气泡,st.chat_input在底部提供输入框。所有对话历史从session_state.app_state.chat_history中读取并展示。这个聊天室的核心任务是承接左列“修正代码”模式发起的对话,或者让用户自由地与AI讨论任何编码问题。
- 左列 - 代码工坊:这里是代码的“生产车间”。顶部通过
这种布局确保了用户视线可以自然地在“生成/查看代码”和“讨论代码”之间切换,模拟了在实际开发中,我们在编辑器和浏览器/文档之间切换的工作流。
3. 核心功能模块的深度实现
3.1 Gemini API客户端的初始化与高效缓存
与Gemini API的交互是整个应用的后端引擎。初始化客户端不是简单调用,需要考虑安全性和性能。
首先,API密钥必须由用户提供。我设计了一个initialize_gemini_client(api_key)函数,它接收前端传入的密钥,使用google.generativeai.configure(api_key=api_key)进行全局配置。这里一个重要的细节是异常处理:如果密钥无效或网络有问题,必须用try...except块捕获google.generativeai.types.GoogleAPIError等异常,并在前端通过st.error友好地提示用户,而不是让整个应用崩溃。
其次,每次用户交互都创建新的客户端和模型实例是巨大的资源浪费。Streamlit提供了@st.cache_resource装饰器,它能够跨会话和重跑缓存返回的对象。因此,我将获取模型客户端的函数get_gemini_client(model_name)用此装饰器包裹。
import streamlit as st import google.generativeai as genai @st.cache_resource def get_gemini_client(model_name=“gemini-2.0-flash”): “”“返回一个缓存的Gemini模型客户端实例。”“” # 注意:api_key需要在调用此函数前,通过genai.configure配置好 model = genai.GenerativeModel(model_name) return model这样,在整个应用生命周期内,对于同一个模型名称,只会初始化一次模型对象,后续调用都是返回缓存实例,极大提升了响应速度并减少了API开销。
3.2 代码生成与解释功能的Prompt工程
AI模型的表现很大程度上取决于我们如何“提问”(即Prompt工程)。对于不同的功能,我设计了差异化的提示模板。
代码生成的Prompt相对直接,但需要包含足够的约束:
请用{language}语言编写代码,实现以下功能:{user_prompt} 要求: 1. 代码需要完整,可以直接运行。 2. 在代码开头添加清晰的注释,说明功能。 3. 输出仅包含代码,不要有任何额外的解释或Markdown格式。关键点在于强调“输出仅包含代码”,并指定语言。这能有效减少模型返回冗余的说明文本,让我们能干净地提取代码块。
代码解释的Prompt则侧重于引导模型进行结构化、易懂的说明:
请详细解释以下{language}代码的功能、逻辑和关键步骤:我特意去掉了“分点说明”这类指令,因为Gemini模型本身在解释代码时已经具备很好的结构化输出能力,过度约束有时反而会让输出变得生硬。实际测试中,简单的指令配合高质量的代码输入,就能得到非常清晰的逐行或分段解释。
3.3 聊天交互中的上下文管理与代码提取
“通过聊天修正代码”是功能最复杂也最体现价值的一环。这里最大的挑战是上下文管理。聊天是连续的,但AI模型本身并不天然记住之前的对话和展示过的代码。
我的实现策略是:当用户选择“Correct Code (via Chat)”模式时,应用会尝试将当前选中的或最新生成的代码片段,作为系统上下文的一部分,在发送给AI的请求中携带。具体来说,在构建发送给send_gemini_message函数的最终Prompt时,我会预置一段话:“用户正在讨论以下代码:[代码内容]。请基于此进行对话和修正。”
更棘手的是如何从AI的回复中自动提取修正后的代码。AI在聊天中返回的通常是包含自然语言和代码块(用```包裹)的Markdown文本。我编写了一个辅助函数extract_code_from_response(response_text),它使用正则表达式(如r“{3}(?:\w+)?\n([\s\S]*?){3}“)来匹配并提取所有代码块。一旦提取成功,就会自动创建一个新的CodeSnippet对象,并添加到app_state.code_snippets列表中,同时在前端给出提示:“检测到新代码,已添加到管理列表”。这样,用户与AI讨论产生的成果就能被无缝捕获和管理。
实操心得:正则表达式提取代码块并非百分百可靠,特别是当AI的回复格式不规则时。因此,这个功能被设计为“尽力而为”。在UI上,我仍然保留了手动复制代码的选项,并鼓励用户在AI返回重要代码时,可以手动点击“添加到片段”按钮。这是一种“自动捕获+手动兜底”的稳健策略。
3.4 代码片段的管理与批量下载机制
管理多个代码片段并允许选择性下载,是这个工具从“玩具”迈向“实用”的关键一步。每个CodeSnippet对象在创建时都会生成一个唯一的ID(我用uuid.uuid4().hex)。在UI上,用一个循环遍历app_state.code_snippets,为每个片段渲染一个st.expander,标题显示文件名和语言,内容区域用st.code高亮显示代码。
每个expander旁边都有一个复选框(st.checkbox),其key与片段的ID绑定,值存储在app_state.selected_snippet_ids(一个Set集合)中。当用户点击“下载”按钮时,后端逻辑会:
- 遍历
selected_snippet_ids,找到对应的CodeSnippet对象。 - 在内存中使用
zipfile.ZipFile创建一个ZIP压缩包。 - 将每个代码片段的内容,以其
filename(确保以.py、.js等结尾)写入到ZIP包中的独立文件。 - 使用Streamlit的
st.download_button,将ZIP文件的二进制数据提供下载。
这个机制使得用户在一次会话中产生的所有代码成果,都可以被系统地归档和带走,非常适合用于保存学习笔记或构建小型代码示例库。
4. 从零开始的完整部署与实操指南
4.1 本地开发环境搭建详解
假设你从零开始,想在自己的机器上运行或修改这个项目。
第一步:获取代码。如果你使用Git,可以直接克隆仓库:git clone https://github.com/djmahe4/CursorAISim.git。如果只是下载,请确保拿到code.py和requirements.txt这两个核心文件。
第二步:创建并激活虚拟环境。这是Python项目的最佳实践,能隔离依赖。在项目根目录下执行:
# 创建虚拟环境,文件夹名为venv python -m venv venv # 激活虚拟环境 # macOS/Linux: source venv/bin/activate # Windows: venv\Scripts\activate激活后,你的命令行提示符前通常会显示(venv),表示已进入虚拟环境。
第三步:安装依赖。确保在虚拟环境激活状态下,安装requirements.txt中列出的所有包:
pip install -r requirements.txtrequirements.txt通常包含:
streamlit>=1.28.0 google-generativeai>=0.3.0 pydantic>=2.0.0 python-dotenv>=1.0.0 # 可选,用于从.env文件加载密钥pydantic用于数据验证,google-generativeai是Gemini官方SDK,python-dotenv可用于更安全地管理API密钥(将密钥放在项目根目录的.env文件中,而不是代码里)。
第四步:配置Gemini API密钥。前往 Google AI Studio ,登录你的Google账号,创建一个API密钥。首次运行streamlit run code.py后,在应用左侧边栏的输入框中粘贴此密钥即可。切勿将密钥直接写入code.py文件或提交到公开仓库。
4.2 核心交互流程的逐步演练
环境准备好后,启动应用:streamlit run code.py。浏览器会自动打开本地地址(通常是http://localhost:8501)。
场景一:快速生成一个Python数据可视化片段。
- 在左侧边栏输入有效的Gemini API密钥。
- 在主区域左列,选择操作模式为“Generate Code”。
- 在“Your Prompt”输入框写下:“用matplotlib画一个正弦波和余弦波的对比图,要求添加图例和网格”。
- 在“Filename”输入“sin_cos_plot.py”,在“Language”选择“python”。
- 点击“Generate Code”按钮。稍等片刻,生成的Python代码就会出现在下方的“Generated/Managed Code Snippets”区域。点击片段标题可以展开查看高亮显示的完整代码。
场景二:理解一段陌生的代码。
- 将操作模式切换为“Explain Code”。
- 将你想理解的代码(比如一段复杂的正则表达式或递归函数)粘贴到大的文本区域中。
- 点击“Explain Code”按钮。AI生成的解释会直接显示在按钮下方,通常会对代码的功能、输入输出、关键行逻辑进行清晰的阐述。
场景三:与AI结对编程,优化代码。
- 首先,用“Generate Code”模式生成一段基础代码,比如一个简单的冒泡排序函数。
- 将操作模式切换到“Correct Code (via Chat)”。此时,最新生成的那个代码片段会自动成为聊天上下文的背景(应用会提示“当前正在讨论的代码片段:…”)。
- 在右侧聊天框输入:“这个排序函数的时间复杂度是多少?能否优化成更快的算法?”
- AI会先分析当前代码的复杂度(O(n^2)),然后可能会直接给出一个快速排序或归并排序的代码示例。如果回复中包含用```包裹的代码块,应用会自动将其提取并作为一个新的代码片段添加到左侧管理区。
- 你可以继续追问:“这个新代码的空间复杂度呢?” 聊天会基于整个对话历史继续进行。
场景四:打包带走你的工作成果。在左侧的代码管理区,勾选你想要的代码片段前的复选框。然后点击下方的“Download Selected Code Snippets (.zip)”按钮,浏览器就会下载一个包含所有选中代码文件的ZIP压缩包。
4.3 模型切换与高级参数调优
项目默认使用gemini-2.0-flash模型,它在速度和成本间取得了良好平衡。如果你想尝试更强大的模型(如gemini-2.0-pro用于更复杂的逻辑推理,或最新的预览版模型),只需修改code.py中初始化模型的一行代码。
找到类似下面的行(通常在get_gemini_client函数内):
model = genai.GenerativeModel(‘gemini-2.0-flash’)将其中的模型名称字符串替换即可,例如:
model = genai.GenerativeModel(‘gemini-2.0-pro’) # 或使用某个预览版 model = genai.GenerativeModel(‘gemini-2.5-flash-preview-04-17’)此外,GenerativeModel的generation_config参数允许你精细控制AI的创造行为。你可以在初始化时传入一个配置字典:
model = genai.GenerativeModel( ‘gemini-2.0-flash’, generation_config={ “temperature”: 0.7, # 控制随机性:0更确定,1更有创意 “top_p”: 0.9, “top_k”: 40, “max_output_tokens”: 2048, # 限制响应长度 } )对于代码生成任务,我通常将temperature设置在0.2到0.5之间,以获得更确定、更符合惯例的代码;对于头脑风暴或寻找多种解决方案,可以调高到0.7以上。
5. 常见问题排查与性能优化经验
5.1 启动与运行时的典型报错处理
在实际使用中,你可能会遇到以下问题:
1.ModuleNotFoundError: No module named ‘google’或‘streamlit’
- 问题:这说明依赖包没有正确安装。
- 解决:首先确认虚拟环境是否已激活(命令行前有
(venv))。然后,在项目根目录下重新执行pip install -r requirements.txt。如果网络问题导致安装失败,可以尝试使用国内镜像源,如pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple。
2.google.generativeai.types.GoogleAPIError: 400 ... API key not valid.
- 问题:最常见的错误,API密钥无效或未设置。
- 解决:a) 检查在Google AI Studio创建的密钥是否复制完整,前后有无空格。b) 确认已在Streamlit应用的侧边栏输入框中正确粘贴了密钥。c) 确保你的Google Cloud项目已启用Gemini API,并且该API密钥未被禁用或限制。
3. 应用界面卡顿,特别是聊天历史很长时
- 问题:Streamlit在每次交互后重跑整个脚本,如果
session_state中存储的数据量很大(比如很长的聊天历史),渲染会变慢。 - 解决:a) 定期使用侧边栏的“Clear Chat”按钮清理历史。b) 从代码层面,可以考虑对非常长的聊天历史进行分页或只保留最近N条消息在
session_state中,将更早的历史记录到文件或数据库。c) 确保没有在每次重跑时进行不必要的昂贵计算,充分利用@st.cache_data和@st.cache_resource。
4. 代码提取功能未能正确识别AI回复中的代码块
- 问题:AI的回复格式可能多变,正则表达式可能匹配失败。
- 解决:首先检查AI的原始回复内容(可以在后端打印日志)。如果代码块使用了非标准的标记(如只有单个反引号),需要调整正则表达式。更稳健的方法是结合使用正则和简单的字符串查找(如查找“```”的索引)。此外,在UI上提供明确的反馈,告知用户“未检测到可提取的代码块,请手动复制”,并保留一个“手动添加当前AI回复为代码片段”的按钮作为备用路径。
5.2 安全性、成本与性能的最佳实践
API密钥安全:这是重中之重。绝对不要将API密钥硬编码在code.py文件中或提交到Git等版本控制系统。最佳实践是:
- 使用环境变量:在运行Streamlit前,在终端设置
export GEMINI_API_KEY=‘your_key_here’(Linux/macOS)或set GEMINI_API_KEY=your_key_here(Windows),然后在代码中用os.getenv(‘GEMINI_API_KEY’)读取。 - 使用
.env文件:安装python-dotenv,在项目根目录创建.env文件写入GEMINI_API_KEY=‘your_key_here’,在code.py开头加载dotenv.load_dotenv()。务必在.gitignore文件中添加.env,防止其被意外提交。
成本控制:Gemini API按每千字符的输入和输出收费。虽然gemini-2.0-flash成本很低,但长时间、高频率使用仍需关注。
- 策略:在开发调试阶段,可以在代码中设置
max_output_tokens限制,避免生成过于冗长的回复。对于解释代码功能,通常不需要非常长的输出。 - 监控:定期在Google AI Studio的API使用仪表盘中查看消耗情况,设置预算提醒。
响应速度优化:
- 缓存一切可缓存的:除了用
@st.cache_resource缓存模型客户端,对于一些不常变化的配置或静态数据,也可以使用@st.cache_data。 - 精简Prompt:在满足需求的前提下,让发送给AI的Prompt尽可能简洁明确,减少不必要的上下文,这能降低输入token数并可能加快AI处理速度。
- 异步处理(高级):对于耗时的AI调用,可以考虑使用异步函数(
async/await)配合Streamlit的st.rerun或实验性功能,防止界面在等待AI响应时完全卡住。不过,这需要更复杂的错误处理和状态管理。
5.3 项目扩展与自定义开发方向
这个基础框架有很大的扩展潜力,你可以根据自己的需求添加功能:
1. 多模型支持与路由可以创建一个模型选择器,让用户在前端选择使用Gemini、OpenAI的GPT(需集成openai库)甚至是本地的开源模型(通过Ollama等工具)。后端设计一个统一的AIClient接口,不同的模型实现该接口,再根据用户选择路由请求。
2. 会话历史持久化目前聊天历史和代码片段只在浏览器会话期间存在,刷新页面就没了。可以集成一个轻量级数据库(如SQLite)或直接读写本地JSON文件。在AppState初始化时尝试从本地加载历史,在每次更新时自动保存。需要处理好并发写入的问题(对于单用户Streamlit应用,通常问题不大)。
3. 代码风格与质量检查在生成或接收代码后,可以调用本地的代码格式化工具(如blackfor Python,prettierfor JS)进行自动格式化。更进一步,可以集成简单的静态分析(如pylint)或安全扫描,让AI助手不仅能写代码,还能初步评估代码质量。
4. 项目级代码管理当前是片段(Snippet)级别的管理。可以扩展为“项目”(Project)概念,一个项目包含多个相关文件(如main.py,utils.py,requirements.txt),并支持整个项目的打包下载和版本快照。
5. 部署到云端想让别人也能用?可以轻松部署到Streamlit Community Cloud、Hugging Face Spaces或任何支持Python的云服务器(如Railway, Render)。部署时,记得将API密钥设置为云平台的环境变量,而不是写在代码里。Streamlit Community Cloud的部署流程非常顺畅,关联GitHub仓库后几乎可以一键完成。
这个项目就像一颗种子,核心的“交互-生成-管理”循环已经跑通。围绕它,你可以根据自己的具体工作流和痛点,添加各种功能,把它打磨成最适合你个人或团队使用的AI编程小助手。
