
从需求到 Windows 交付,用 Codex 完成一个真正可运行的聚合搜索桌面应用
Codex实战 从0到1开发聚合搜索桌面应用 需求拆解、智能编码、测试迭代与Windows交付
Codex AI智能体 AI编程 聚合搜索 Python PySide6 Windows桌面应用 软件工程 自动化测试 项目实战
AI 编程真正有价值的地方,不是“帮你补几行代码”,而是把需求、设计、实现、验证和交付串成一条可反复迭代的工程链路。本文以 Windows 桌面聚合搜索工具为完整案例:从需求清单和验收标准开始,借助 Codex 规划项目结构、生成 PySide6 桌面界面、抽象可配置搜索源、实现快捷键唤醒与浏览器跳转,再通过测试矩阵、截图反馈、异常排查和 PyInstaller 打包完成交付。你会看到哪些任务适合直接交给智能体,哪些关键决策必须由开发者把关,以及如何把“一次能跑”升级为“结构清楚、可验证、可扩展、能长期维护”的桌面应用。
|
先看最终效果 完成后,你会得到一个可在 Windows 本地运行的搜索中枢:按快捷键呼出窗口,输入一次关键词,在不同搜索入口间切换;搜索源由配置文件驱动,新增入口无需修改核心逻辑;应用可打包为独立可执行程序。 |
你将完成什么
|
阶段 |
核心产物 |
验收信号 |
|
01 需求 |
需求清单 + 验收标准 |
知道什么叫“做完” |
|
02 设计 |
目录结构 + 搜索源模型 |
新增入口不改核心逻辑 |
|
03 开发 |
PySide6 桌面程序 |
主流程可运行 |
|
04 体验 |
快捷键 + 焦点 + 自动隐藏 |
高频操作顺手 |
|
05 验证 |
测试矩阵 + 回归检查 |
每次改动有证据 |
|
06 交付 |
PyInstaller 打包 |
脱离开发环境可启动 |

图1|项目全景:自然语言需求 → 规划 → 编码 → 测试 → Windows 交付
一、为什么这个项目适合用 Codex 做一次完整实战
聚合搜索看起来只是“输入关键词,然后打开某个网站”,但只要把它做成每天都愿意使用的桌面工具,就会同时遇到产品、交互、配置、系统集成、测试和打包问题。它足够小,可以在一篇文章里讲清楚;又足够完整,能验证 AI 编程智能体是否真的能参与软件工程,而不是只生成代码片段。
本文的目标不是抓取各网站结果并重新展示。更稳妥的第一版是“搜索入口聚合器”:本地负责统一输入、分类、选择和配置,最终把经过 URL 编码的查询交给用户的默认浏览器。这样能避免把项目一开始就拖入页面解析、反爬、登录态、版权和接口稳定性等复杂问题。
|
工程取舍 先把高频主链路做得稳定,再扩展 AI 总结、网页抓取或自定义插件。一个可维护的小闭环,比功能很多但不可验证的“大而全”原型更有价值。 |
1.1 当前 Codex 的使用方式
截至 2026 年 9 月,Codex 已经融入桌面端的开发工作流,可用于管理项目、并行处理任务、审查差异、运行命令和执行较长时间的工程任务。本文采用的协作方式是:把项目目录交给 Codex,把需求和约束写进仓库文件,让智能体围绕真实文件持续工作,而不是每轮对话重新解释上下文。
1.2 这次不追求“一句话生成全部代码”
- 先定义产品目标和不做什么,避免智能体自行扩张范围。
- 先让 Codex 输出计划,再允许修改文件,降低返工。
- 每完成一个里程碑就运行程序和测试,失败立即修复。
- UI 问题优先用截图 + 位置 + 期望效果描述,减少歧义。
- 把长期有效的约束写进项目文件,让后续迭代不漂移。

图2|总体架构:界面、路由、配置和系统能力解耦
二、先写需求:把“我想做个搜索工具”变成可验收规格
给智能体的第一份输入,最好不是一句“帮我写个聚合搜索软件”,而是一份可以被检查的规格。需求越具体,Codex 越容易做出第一版就接近目标的实现。
2.1 MVP 功能清单
- 一个简洁的桌面窗口,打开后输入框自动获得焦点。
- 搜索源按“常用、技术、学术、AI、视频”等分类展示。
- 输入关键词后,点击搜索源或按 Enter,在默认浏览器打开对应查询。
- 搜索源由 JSON 配置驱动,至少包含名称、分类、URL 模板、启用状态。
- 支持键盘上下/左右切换搜索源,Enter 执行。
- 支持全局快捷键呼出;执行搜索后可选择自动清空并隐藏。
- 保存最近搜索词,但允许一键清空历史。
- 可打包为 Windows 可执行程序;资源路径在打包后仍正确。
2.2 明确第一版不做什么
- 不绕过登录、验证码或站点访问限制。
- 不批量抓取搜索结果页,也不复制第三方页面内容。
- 不在第一版引入数据库;设置和历史先用本地 JSON。
- 不为了“看起来高级”引入复杂微服务、消息队列或云基础设施。
- 不把 API Key、密码等敏感信息写进源码或仓库。
2.3 验收标准
|
场景 |
操作 |
通过条件 |
|
正常搜索 |
输入“Python asyncio”并选择技术搜索 |
打开的 URL 包含正确编码后的关键词 |
|
特殊字符 |
输入“C++ & Rust” |
不会截断参数或报错 |
|
空输入 |
直接执行搜索 |
界面提示输入关键词,不打开浏览器 |
|
快捷键 |
窗口隐藏时按设定热键 |
窗口出现、前置、输入框聚焦 |
|
配置扩展 |
JSON 新增一个搜索源 |
重启后自动出现,无需改 Python |
|
打包 |
在无源码目录运行 exe |
窗口、图标、配置均正常 |
三、项目初始化:让 Codex 先规划,再动手
建议先建立一个空目录,例如 aggregate-search-desktop,然后让 Codex 读取该目录。第一轮不要直接要求“开始写代码”,而是要求它基于规格给出目录结构、依赖、风险和实施顺序。
3.1 第一轮提示词
你是这个项目的主程。请先不要修改任何文件。
目标:在 Windows 上开发一个 PySide6 聚合搜索桌面应用。
请先完成:
1. 复述需求与边界;
2. 给出推荐目录结构;
3. 说明核心模块职责;
4. 列出依赖及其必要性;
5. 给出分阶段实现计划和每阶段验收方法;
6. 指出全局快捷键、资源路径、打包最容易出问题的地方。
要求:优先简单、可维护、可测试的方案,不要过度设计。
如果计划里出现数据库、Web 后端、复杂插件系统等当前并不需要的组件,直接要求删掉。AI 的方案不是结论,而是待审查的设计草案。
3.2 推荐目录结构
aggregate-search-desktop/
├─ app.py
├─ requirements.txt
├─ README.md
├─ AGENTS.md
├─ config/
│ ├─ providers.json
│ └─ settings.json
├─ src/
│ ├─ main_window.py
│ ├─ provider.py
│ ├─ provider_registry.py
│ ├─ search_service.py
│ ├─ settings_service.py
│ └─ hotkey_service.py
├─ assets/
│ └─ app.ico
└─ tests/
├─ test_provider.py
├─ test_search_service.py
└─ test_settings.py
这里最重要的不是文件名,而是边界:UI 不负责拼接 URL;搜索服务不关心窗口长什么样;配置加载失败时有默认值;全局快捷键被封装在独立服务中。

图3|Codex 协作开发闭环:规格、实现、验证、反馈必须形成循环
四、把长期约束写进 AGENTS.md
长任务最容易出现的不是“不会写代码”,而是上下文漂移:前面约定 PySide6,后面突然换框架;前面要求配置化,后面又把搜索源硬编码回 UI。解决方法是把稳定约束写进项目文件,让每轮工作都能重新读取。
# Project Rules
## Goal
Build a lightweight Windows desktop search launcher.
## Architecture
– UI: PySide6
– Provider definitions: JSON
– Search execution: open encoded URL in default browser
– No scraping in MVP
– No database in MVP
## Quality Gate
Before reporting a task complete:
1. Run unit tests.
2. Launch the app for a smoke test.
3. Verify empty input and special-character queries.
4. Do not silently change public behavior.
5. Keep provider-specific logic out of the UI.
## Security
– Never commit secrets.
– Do not disable OS security controls.
– Do not execute destructive commands unless explicitly requested.
这类文件相当于项目的“长期记忆”。它不能替代具体任务提示,但能让智能体在多轮修改中保持架构和验收标准稳定。
五、实现搜索源模型:先把最容易变化的部分配置化
5.1 providers.json
[
{
"id": "web_a",
"name": "通用搜索 A",
"category": "常用",
"url_template": "https://example.com/search?q={query}",
"enabled": true
},
{
"id": "dev_a",
"name": "开发资源",
"category": "技术",
"url_template": "https://example.dev/search?q={query}",
"enabled": true
},
{
"id": "paper_a",
"name": "学术检索",
"category": "学术",
"url_template": "https://example.edu/search?query={query}",
"enabled": true
}
]
示例域名只是展示配置结构。实际使用时,将 url_template 替换为目标服务公开、稳定、允许用户直接访问的搜索地址。
5.2 Provider 数据类
from dataclasses import dataclass
@dataclass(frozen=True)
class Provider:
id: str
name: str
category: str
url_template: str
enabled: bool = True
def validate(self) -> None:
if not self.id.strip():
raise ValueError("provider id 不能为空")
if "{query}" not in self.url_template:
raise ValueError("url_template 必须包含 {query}")
5.3 SearchService:统一编码,统一打开
from urllib.parse import quote_plus
from PySide6.QtCore import QUrl
from PySide6.QtGui import QDesktopServices
class SearchService:
@staticmethod
def build_url(provider, query: str) -> str:
query = query.strip()
if not query:
raise ValueError("请输入搜索关键词")
encoded = quote_plus(query, safe="")
return provider.url_template.replace("{query}", encoded)
@classmethod
def open_search(cls, provider, query: str) -> str:
url = cls.build_url(provider, query)
if not QDesktopServices.openUrl(QUrl(url)):
raise RuntimeError("系统无法打开默认浏览器")
return url
把 URL 构造集中到一个服务里有两个好处:一是所有入口都使用同一套编码规则;二是单元测试不需要真的打开浏览器,只测试 build_url 即可。

图4|第一版交互重点:一个输入框、多类搜索入口、清晰的执行反馈
六、搭建 PySide6 主界面:先保证主链路顺滑
桌面搜索工具的核心不是复杂视觉,而是“呼出快、输入快、选择快、离开快”。因此第一版 UI 建议只保留搜索框、分类区、搜索源卡片、状态提示和设置入口。
6.1 主窗口关键逻辑
from PySide6.QtWidgets import QMainWindow, QLineEdit, QMessageBox
class MainWindow(QMainWindow):
def __init__(self, registry, search_service):
super().__init__()
self.registry = registry
self.search_service = search_service
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("输入关键词,选择搜索入口…")
self.search_input.returnPressed.connect(self.search_current)
def show_and_focus(self):
self.show()
self.raise_()
self.activateWindow()
self.search_input.setFocus()
self.search_input.selectAll()
def search_current(self):
provider = self.current_provider()
if provider is None:
return
try:
self.search_service.open_search(
provider, self.search_input.text()
)
except Exception as exc:
QMessageBox.warning(self, "搜索失败", str(exc))
return
if self.auto_hide_enabled():
self.search_input.clear()
self.hide()
“搜索后清空并隐藏”是一个很小的功能,却决定了工具是否适合高频使用。执行一次搜索后,用户的注意力已经转移到浏览器,桌面窗口继续占据前台反而增加干扰。
6.2 键盘优先
- 窗口出现后立即聚焦输入框,不让用户再点一次鼠标。
- Enter 执行当前入口;Esc 隐藏窗口。
- 方向键或数字快捷键切换入口。
- Tab 顺序必须可预测,避免焦点跳到不可见控件。
- 分类切换后保留关键词,方便同一问题跨入口比较。
七、全局快捷键:把“打开应用”缩短成一次按键
聚合搜索工具真正变成“桌面中枢”的关键,是在任何窗口中都能快速呼出。实现时要注意:全局热键属于系统级能力,可能受权限、冲突和安全软件影响,因此必须提供失败提示和可修改设置。
7.1 设计建议
- 默认组合不要占用常见系统快捷键,例如可使用 Ctrl + Alt + Space。
- 注册失败时在设置页明确显示“快捷键已被占用”,不要静默失败。
- 允许用户修改组合,并在保存时先注销旧热键再注册新热键。
- 回调线程不要直接修改 Qt UI,应通过 Signal 切回主线程。
# 伪代码:具体全局热键库可按项目环境选择
class HotkeyBridge(QObject):
triggered = Signal()
def on_global_hotkey(self):
self.triggered.emit()
# 主线程
bridge.triggered.connect(main_window.show_and_focus)
如果 Codex 生成了“在热键回调线程里直接 show() 窗口”的实现,应要求它修正。GUI 框架通常要求界面操作发生在主线程,这是典型的“代码看起来合理、运行时偶发异常”的问题。
八、第二轮迭代:用截图而不是抽象形容词改 UI
第一版能跑后,最容易浪费时间的提示是“再高级一点”“更好看一点”。这些描述无法形成稳定目标。更有效的方法是:截图圈出具体区域,写清现象、期望和不能改变的行为。
8.1 一次有效的 UI 反馈
请只修改界面,不改变搜索逻辑。
问题 1:搜索源卡片间距过大,窗口显得松散。
期望:同一分类尽量在首屏展示 6~8 个入口。
问题 2:当前选中入口不明显。
期望:选中项使用更清晰的描边和背景,同时保持文字可读。
问题 3:窗口弹出后输入框偶尔没有焦点。
期望:每次通过快捷键唤醒都必须聚焦输入框。
完成后:
– 启动应用做一次手工 smoke test;
– 不要修改 providers.json 的字段结构;
– 汇报修改文件和验证结果。
注意最后三行:限制修改范围、要求运行验证、要求汇报证据。它们能显著减少“为了改样式顺手重构业务逻辑”的风险。

图5|迭代后的目标:搜索入口清晰、状态明确、主任务始终突出
九、测试:把“我试过了”升级成可重复的验收
AI 生成代码最需要补强的环节不是继续生成,而是验证。一个修改如果没有测试或运行证据,就只能说明“看起来可能对”。本文至少覆盖纯逻辑单测、主流程 smoke test、异常输入和打包后运行四层。
9.1 URL 构造单元测试
def test_build_url_encodes_query():
provider = Provider(
id="demo",
name="Demo",
category="常用",
url_template="https://example.com/search?q={query}",
)
url = SearchService.build_url(provider, "C++ & Rust")
assert url == "https://example.com/search?q=C%2B%2B+%26+Rust"
def test_build_url_rejects_empty_query():
provider = Provider(
id="demo",
name="Demo",
category="常用",
url_template="https://example.com/search?q={query}",
)
try:
SearchService.build_url(provider, " ")
except ValueError as exc:
assert "请输入搜索关键词" in str(exc)
else:
raise AssertionError("空关键词应该被拒绝")
9.2 配置测试
- 缺少 id:拒绝加载并指出具体条目。
- url_template 没有 {query}:拒绝启动该入口。
- enabled=false:入口不出现在界面。
- 未知 category:放入“其他”而不是崩溃。
- 配置文件损坏:使用内置默认配置并提示用户修复。

图6|验收矩阵:每个核心能力都有明确动作和通过条件
十、常见故障与定位顺序
排错时不要把整段报错扔给智能体后让它“随便修”。先给环境、复现步骤、完整异常、最近改动和预期行为,Codex 才能缩小搜索空间。
|
现象 |
高概率原因 |
定位顺序 |
|
点击后浏览器没反应 |
URL 无效 / 系统默认浏览器异常 |
打印最终 URL → 手工打开 → 检查 QDesktopServices 返回值 |
|
中文正常、特殊字符失败 |
URL 编码不统一 |
只保留一个 build_url 实现 → 增加 C++ & Rust 测试 |
|
快捷键偶尔无效 |
冲突 / 注册失败 / 线程问题 |
记录注册结果 → 换组合键 → 检查 UI 主线程 |
|
打包后图标或配置丢失 |
相对路径基于工作目录 |
统一 resource_path → 在临时目录启动 exe 验证 |
|
开发环境正常、exe 崩溃 |
隐藏依赖 / 插件缺失 |
查看控制台日志 → PyInstaller hidden-import → clean build |
|
UI 修改后搜索失效 |
界面与业务耦合 |
检查 signal 绑定 → 回归 SearchService 测试 |
10.1 给 Codex 的排错提示词模板
请先定位原因,不要直接大范围重构。
环境:
– Windows 11
– Python 3.12
– PySide6
– 当前分支可启动
复现步骤:
1. 启动应用
2. 输入:C++ & Rust
3. 选择“开发资源”
4. 按 Enter
实际现象:
浏览器打开,但查询参数只剩 C++
预期:
完整搜索 C++ & Rust
请:
1. 找到最终 URL 的构造路径;
2. 写一个能复现问题的测试;
3. 最小修改修复;
4. 运行相关测试;
5. 说明根因和修改文件。
十一、Windows 打包:最后一公里不能只看“生成成功”
打包成功并不等于交付成功。真正的验收是:把 exe 放到一个干净目录,最好在没有源码、没有虚拟环境的情况下启动,检查图标、配置、快捷键、浏览器跳转和异常提示。
11.1 基础打包命令
pyinstaller ^
–noconfirm ^
–clean ^
–windowed ^
–name AggregateSearch ^
–icon assets/app.ico ^
–add-data "config;config" ^
app.py
Windows 下 –add-data 的分隔符通常使用分号。项目若包含 Qt 插件、额外动态库或第三方热键库,需根据实际构建日志补充隐藏依赖。不要直接复制别人项目的 hidden-import 清单。
11.2 资源路径
from pathlib import Path
import sys
def resource_path(relative: str) -> Path:
base = Path(getattr(sys, "_MEIPASS", Path(__file__).resolve().parent))
return base / relative
开发态和打包态的根目录可能不同,所有图标、默认配置和静态资源都应通过统一函数解析,避免在代码里到处拼相对路径。
11.3 交付前清单
- 首次启动是否有明显等待或黑窗。
- 在中文 Windows 用户目录下是否正常。
- 路径包含空格时是否正常。
- 配置文件缺失或损坏时是否可恢复。
- 快捷键冲突时是否给出可理解提示。
- 杀毒软件或系统策略拦截时是否有替代启动方式。
- 版本号、日志位置和卸载方式是否清楚。

图7|从代码到产品:实现、架构、测试和交付必须同时成立
十二、继续升级:从搜索启动器走向个人信息入口
第一版稳定后,扩展应该沿着“可选、可关闭、不破坏主链路”的方向进行。以下能力都适合作为独立模块加入,而不是一次性塞进核心窗口。
|
扩展 |
价值 |
实现重点 |
风险 |
|
自定义搜索源 |
适配个人工作流 |
可视化编辑 JSON |
错误模板导致无效 URL |
|
搜索历史 |
快速复用高频查询 |
本地存储 + 一键清空 |
隐私与敏感词 |
|
AI 查询改写 |
把模糊问题变成检索词 |
显式开关 + 原词保留 |
改写偏离意图 |
|
多入口并行打开 |
一次对比多个来源 |
数量限制 + 确认 |
瞬间打开过多标签页 |
|
插件机制 |
接入团队内部工具 |
稳定接口 + 权限边界 |
复杂度迅速上升 |
|
同步配置 |
多设备一致 |
加密与冲突解决 |
账号与数据安全 |
12.1 AI 总结应该放在哪一层
如果未来要做“多来源结果 → AI 总结”,建议把它设计成独立能力,而不是让 UI 直接调用模型。输入必须来自明确、允许使用的数据源,并保留来源链接;输出要区分“原始事实”和“模型归纳”。这样既方便替换模型,也方便关闭 AI 功能后继续使用基础搜索。
十三、这个项目真正训练的不是提示词,而是工程判断
做完这个项目后,最值得复用的并不是某一段 PySide6 代码,而是一套和智能体协作的开发方式:先把问题定义清楚,再让 AI 扩大执行能力;把易变内容配置化,把核心逻辑做成可测试模块;每次迭代都用证据收口;最后在真实交付环境里验收。
- 需求写成可验收的行为,而不是形容词。
- 先计划再编码,先最小闭环再扩功能。
- 把长期约束写入仓库,而不是依赖聊天记忆。
- 把截图、日志、测试结果当成反馈证据。
- 任何“完成”都必须对应运行、测试或交付验证。
- 让 AI 负责高吞吐执行,让人负责边界、风险和最终判断。
十四、完整实战提示词清单
下面这组提示词可以按阶段使用。它们的共同点是:目标明确、约束明确、验收明确。
14.1 规划阶段
阅读 README.md、AGENTS.md 和当前目录。
先不要写代码。
请输出:
– 需求理解
– MVP 与非目标
– 模块边界
– 文件结构
– 依赖
– 风险
– 6 个以内里程碑
– 每个里程碑的验证方式
如果有不必要的复杂设计,请主动删减。
14.2 第一版实现
按已确认计划实现里程碑 1~3。
要求:
– PySide6;
– 搜索源来自 JSON;
– UI 不拼接 URL;
– 空输入有提示;
– 特殊字符正确编码;
– 为纯逻辑写单元测试。
完成后运行测试并启动应用做 smoke test,再汇报结果。
14.3 UI 迭代
只调整 UI 与交互,不改变 Provider/Registry/SearchService 公共行为。
根据我提供的截图逐项修复。
完成后验证:
1. 快捷键唤醒;
2. 输入框焦点;
3. Enter 搜索;
4. Esc 隐藏;
5. 原有单元测试全部通过。
不要顺手重构无关文件。
14.4 打包阶段
为当前 Windows 项目补齐 PyInstaller 打包。
目标:在没有源码和虚拟环境的干净目录中运行。
请先检查:
– 静态资源路径
– Qt 插件
– 配置文件
– 隐藏依赖
– windowed 模式下日志方案
然后生成构建命令/脚本,执行 clean build,并做打包后 smoke test。
十五、结语
AI 编程进入智能体阶段之后,开发者的价值并没有被“按下生成按钮”取代,反而更集中在定义问题、审查方案、控制边界和验证结果上。Codex 可以很快把一个想法变成可运行代码,但真正决定项目质量的,是你是否给出了稳定的规格、是否要求它用测试证明修改、是否在真实环境完成交付。
聚合搜索应用只是一个入口。掌握这套闭环后,同样的方法可以迁移到日志分析器、批量文件工具、数据清洗桌面程序、内部知识检索、自动化运维面板等大量轻量应用:把目标写清楚,把上下文放进项目,把任务拆成里程碑,让智能体执行,让测试和运行结果说话。
|
最终原则 不要追求“一次生成完美项目”。追求的是:每一轮都比上一轮更接近可验证、可维护、可交付。 |

图8|最终落点:稳定主链路 + 清晰边界 + 可配置扩展
网硕互联帮助中心





评论前必须登录!
注册