← Back to 文字

CLI 入口实现

本文来自《AI 应用开发课程》月份 1 课程文档,已整理为网站文章版本。

学习目标

学完本节后,你应当能够:

  • 为研发助手增加命令行入口。
  • 让 CLI 只负责参数接收和结果展示。
  • 保证 CLI 不复制业务逻辑。

前置知识

  • 已完成项目需求与架构设计

1. 为什么要有 CLI

因为它是最快的使用与验证方式:

  • 不需要前端
  • 不需要接口调试工具
  • 更适合研发场景

月份 1 用 CLI 可以快速验证核心服务是否合理。

2. CLI 层的职责

只做以下事情:

  • 接收命令参数
  • 调用 service
  • 打印结果

不要:

  • 直接拼模型请求
  • 直接写工具注册逻辑
  • 直接处理底层异常细节

3. 推荐命令设计

例如:

uv run python -m app.cli.main summarize-diff --file sample.diff
uv run python -m app.cli.main extract-todos --text "请实现登录接口并补测试"

4. 典型实现结构

app/cli/
├── __init__.py
└── main.py

main.py 中:

  • 解析参数
  • 创建 service
  • 调用 service
  • 打印结构化结果

5. 输出建议

CLI 输出要可读,但不要花哨。

建议至少输出:

  • 功能名
  • 输入摘要
  • 模型输出
  • 如果失败则输出错误信息

6. 实操任务

  1. 先做一个最小命令
  2. 把“抽取待办项”接入 CLI
  3. 再接一个“生成 commit message”或“总结错误日志”

7. 自测题

  1. 为什么 CLI 是月份 1 的理想第一入口?
  2. 为什么 CLI 不应该持有完整业务逻辑?
  3. CLI 层最应该复用哪一层?

8. 验收标准

  • 至少一个命令可执行
  • 能看到结构化输出
  • 核心逻辑来自 service 层而不是 CLI 层

9. 本章与前文关系

上一章已经把综合项目的范围和架构定下来了。现在进入真正落地阶段时,最先做 CLI,而不是最先做 API,是一个非常刻意的教学选择。

原因很简单:

  • CLI 更轻
  • 调试成本更低
  • 更适合快速验证业务层

这会帮助你先把核心业务跑顺,再去处理 HTTP 细节。

10. 本章在研发助手项目中的位置

CLI 是研发助手最贴近开发者工作流的入口。它特别适合:

  • 本地快速调用
  • 录屏展示
  • 调试 Prompt 和 Tool Calling
  • 在没有前端时先验证主链路

CLI 不是临时玩具,而是项目第一阶段最实用的入口。

11. 一个更完整的 CLI 文件级示例

app/cli/main.py

"""研发助手 CLI 入口。"""

from __future__ import annotations

import argparse

from app.clients.llm_client import LLMClient
from app.models import ChatMessage, ChatRequest
from app.services.chat_service import ChatService


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="rd-assistant CLI")

    parser.add_argument(
        "--task",
        required=True,
        help="请输入要让研发助手处理的任务文本",
    )

    return parser


async def run_task(task_text: str) -> None:
    llm_client = LLMClient()
    chat_service = ChatService(llm_client=llm_client)

    request = ChatRequest(
        messages=[
            ChatMessage(role="system", content="你是一个严谨的研发助手。"),
            ChatMessage(role="user", content=task_text),
        ]
    )

    response = await chat_service.chat(request)
    print("=== 模型输出 ===")
    print(response.answer)


def main() -> None:
    parser = build_parser()
    args = parser.parse_args()

    import asyncio

    asyncio.run(run_task(args.task))


if __name__ == "__main__":
    main()

12. 逐段解释这份完整示例

为什么 CLI 层只解析参数

因为参数解析本身是入口职责,不应该和业务逻辑混在一起。

为什么 run_task 里直接组装 ChatRequest

因为 CLI 的职责是把用户输入变成业务层可接受的结构,而不是直接处理 HTTP 或模型细节。

为什么最终调用的是 chat_service.chat()

因为这正是你在为后面 FastAPI 入口做复用准备。

13. 一个更接近项目主线的增强方向

你可以继续增加不同命令,例如:

  • extract-todos
  • generate-commit-message

但月份 1 建议保持一个简单原则:

  • 命令数量可以慢慢加
  • 核心 service 不要分裂成多套重复实现

14. 调试与排错:本章最常见问题

问题一:CLI 看起来能跑,但逻辑高度耦合

通常说明你把太多业务逻辑塞进了 main.py

问题二:异步入口报错

通常是事件循环使用不当,或者把异步逻辑直接放到了同步流程里。

问题三:参数解析和业务结构没有对齐

这会让后面 FastAPI 接入时不得不重写一套转换逻辑。

15. 本章完成后你应该具备的能力

完成本章后,你应当做到:

  1. 能为综合项目提供一个最小 CLI 入口。
  2. 能让 CLI 复用核心 service。
  3. 能把“入口层薄、业务层厚”的原则落实到代码。

16. 从本章过渡到下一章的桥接说明

接下来进入 03-FastAPI入口实现.md

因为你现在已经有了一条 CLI 驱动的主链路。下一步最自然的动作,就是让 FastAPI 去复用这条链路,把同样的能力暴露成服务接口。

Fin