适合个人开发者、AI 工具作者、脚本自动化玩家。
如果你现在的项目还把 API Key、请求逻辑、业务逻辑全写在一个文件里,这篇文章可以直接改掉你的写法。

为什么项目一开始就要拆结构?

很多人做项目的时候,第一版通常都很简单:

  • 一个 main.py
  • 里面直接写请求
  • Key 也写死在里面
  • 业务逻辑和接口调用混在一起

这种写法能跑,但很快就会出现问题:

  • 代码越来越乱
  • 修改一个地方要翻很多行
  • 换模型、换接口、换配置都很麻烦
  • 出错后不好排查

所以如果你做的是 AI 工具、自动化脚本、API 接入项目,最好从一开始就把结构拆开。

这篇文章直接给你一个适合个人开发者的最小结构方案。


一、先说结论:推荐的项目结构

你可以把项目拆成这几块:

project/
├── .env
├── config.py
├── llm_client.py
├── main.py
├── requirements.txt
└── logs/

每个文件干什么?

  • .env:放密钥、地址、模型名
  • config.py:统一读取配置
  • llm_client.py:封装 API 调用
  • main.py:写业务逻辑
  • logs/:放日志

这种结构不复杂,但后期很好维护。


二、为什么不建议把所有代码写在一个文件里?

1. 维护麻烦

你一旦把 API 调用、错误处理、业务逻辑、配置读取全写在一起,后面改起来会很痛苦。

2. 不方便复用

如果你以后还想做第二个项目,很多代码没法直接拿过来。

3. 不利于排错

出问题时,你根本不容易判断是配置错了,还是请求错了,还是业务逻辑错了。

4. 不适合扩展

你后面一旦加重试、缓存、日志、限流,这种单文件结构会越来越乱。


三、.env 里放什么最合适?

建议把这些放进去:

API_KEY=***
BASE_URL=https://your-api-domain.com/v1
MODEL=your-model-name
TIMEOUT=20
MAX_RETRIES=3

这样做的好处

  • 不把敏感信息写死在代码里
  • 本地和线上可以切换配置
  • 修改参数时不用动业务代码

四、config.py 怎么写?

config.py 的作用就是统一读取配置并做校验。

import os
from dotenv import load_dotenv

load_dotenv()


def get_config():
    api_key = os.getenv("API_KEY")
    base_url = os.getenv("BASE_URL")
    model = os.getenv("MODEL")
    timeout = int(os.getenv("TIMEOUT", "20"))
    max_retries = int(os.getenv("MAX_RETRIES", "3"))

    if not api_key:
        raise ValueError("API_KEY is required")
    if not base_url:
        raise ValueError("BASE_URL is required")
    if not model:
        raise ValueError("MODEL is required")

    return {
        "api_key": api_key,
        "base_url": base_url,
        "model": model,
        "timeout": timeout,
        "max_retries": max_retries,
    }

这个文件的作用

  • 统一读取环境变量
  • 启动时提前发现问题
  • 避免 Key 为空还继续跑

五、llm_client.py 怎么封装最舒服?

这里建议把所有 API 调用都放在一个地方。

import time
from openai import OpenAI
from openai import APIError, APIConnectionError, APITimeoutError, RateLimitError
from config import get_config

config = get_config()

client = OpenAI(
    api_key=config["api_key"],
    base_url=config["base_url"],
    timeout=float(config["timeout"]),
)


def ask_llm(prompt: str) -> str:
    last_error = None

    for attempt in range(1, config["max_retries"] + 1):
        try:
            response = client.chat.completions.create(
                model=config["model"],
                messages=[
                    {"role": "system", "content": "你是一个专业的技术助手。"},
                    {"role": "user", "content": prompt},
                ],
            )
            return response.choices[0].message.content

        except (APIConnectionError, APITimeoutError, RateLimitError, APIError) as e:
            last_error = e
            if attempt < config["max_retries"]:
                wait = 2 * attempt
                print(f"第 {attempt} 次失败,{wait} 秒后重试:{e}")
                time.sleep(wait)
            else:
                print(f"重试结束,最终失败:{e}")

    raise RuntimeError(f"请求失败:{last_error}")

为什么这样封装?

因为你后面只要改这个文件,就能影响整个项目的调用行为。


六、main.py 只负责业务逻辑

main.py 不要再管配置,不要再管重试,不要再管细节调用。

from llm_client import ask_llm


def main():
    question = "给我写一个 Flask 接口示例"
    answer = ask_llm(question)
    print(answer)


if __name__ == "__main__":
    main()

这样写的好处

  • 主入口非常清楚
  • 业务逻辑和基础设施分离
  • 后面加更多功能也不乱

七、这套结构适合哪些项目?

特别适合这些场景:

  • AI 工具站
  • 自动化脚本
  • Agent 工作流
  • 文本生成项目
  • 个人效率工具
  • 技术副业项目

如果你后面还打算继续迭代,这种拆法会比单文件强很多。


八、几个很容易踩坑的地方

1. 不要把 Key 写死在代码里

一定放 .env

2. 不要把请求逻辑散落在各处

统一封装到一个文件里。

3. 不要把业务和基础设施混在一起

main.py 只做流程控制。

4. 不要忘了做配置校验

启动时报错总比运行半天才发现问题好。


九、如果你后面要扩展,还可以继续加什么?

当项目变大后,你还可以继续加:

  • logger.py:统一日志
  • cache.py:缓存结果
  • retry.py:单独抽重试逻辑
  • api/:不同接口模块化
  • tests/:测试用例

但对于个人开发者来说,先把上面这套最小结构跑通就够了。


十、结语

很多项目后面不好维护,不是因为功能太复杂,而是一开始就把所有东西写在一起。

如果你能从第一天就把:

  • 配置
  • 请求
  • 业务
  • 日志

分开处理,后面会省很多时间。

如果你也在做 AI 工具、脚本自动化或者个人项目,可以留言或私信,我可以把我整理好的项目模板发给你。

免责声明

本文内容仅用于技术交流与经验分享,不构成任何商业承诺。具体使用效果请以实际测试为准。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐