这是一篇实战记录:用 DeepSeek + deepagents 搭一个领域专家智能体,并用 .env 管理配置。文中会逐一说明用到了哪些组件、每个组件负责什么,以及搭建过程中踩过的坑。


1. 这个智能体是什么

一句话:一个会以「铁矿石价格预测专家」身份回答问题的 AI 智能体

你问它「近期影响铁矿石价格的核心因素」,它会从供给、需求、库存、钢厂利润、宏观汇率、衍生品等维度,按「结论 → 驱动因素 → 假设 → 风险 → 置信度」的结构专业作答,并自带免责声明。

跑起来就这么一句:

uv run python -m mystu.buildagent.agent.deepagent

2. 整体架构

智能体不是「一个文件」,而是几层组件拼起来的。先看目录:

mystu/                              # 项目根目录(含 .env、pyproject.toml)
├── .env                            # 配置文件:模型、API Key、参数
├── pyproject.toml                  # 依赖清单(类似 Java 的 pom.xml)
└── mystu/                          # Python 包
    └── buildagent/
        ├── agent/
        │   ├── __init__.py         # 包初始化:自动加载 .env
        │   ├── modelConfig.py      # 配置层:读取并校验 .env
        │   └── deepagent.py        # 组装层:把模型 + 提示词拼成 Agent
        └── prompt/
            ├── system.py           # 提示词加载器(Jinja2 渲染)
            └── md/
                └── iron_ore_forecast.md   # 铁矿石专家的系统提示词

数据流(从配置到回答):

.env 文件
   │  load_dotenv()        ← python-dotenv:把配置读进环境变量
   ▼
modelConfig.py (LLMConfig) ← pydantic:校验配置、给默认值
   │  build_model()
   ▼
ChatDeepSeek               ← langchain-deepseek:连接 DeepSeek API
   │
   ├── iron_ore_forecast.md ← jinja2:渲染系统提示词(人设)
   ▼
create_deep_agent()        ← deepagents:组装成带工具的智能体
   │  agent.invoke(...)
   ▼
DeepSeek 模型              ← deepseek-v4-pro:真正生成回答
   │
   ▼
专业的铁矿石分析回答

3. 用到了哪些组件(总览)

组件 角色 一句话职责
python-dotenv 配置加载 .env 文件里的配置读进环境变量
pydantic 配置校验 给配置定义结构、类型、默认值,缺关键项就报错
jinja2 提示词模板 .md 模板渲染成最终的系统提示词,支持变量
langchain-deepseek 模型接入 提供 ChatDeepSeek,按 OpenAI 兼容协议连 DeepSeek
DeepSeek (deepseek-v4-pro) 大模型 真正“思考”和生成回答的大脑
deepagents 智能体框架 把模型 + 提示词 + 工具(待办/文件/shell/子代理)组装成 Agent
langchain / langgraph 底座 deepagents 构建于其上,提供消息、模型抽象与图式编排

下面逐层拆开讲。


4. 逐层拆解

4.1 配置层:python-dotenv + pydantic

它们解决什么问题:把「会变的东西」(模型名、API Key、温度等)从代码里抽出来,集中放到 .env,改配置不用改代码。

.env 长这样:

LLM_PROVIDER=deepseek
LLM_MODEL=deepseek-v4-pro
LLM_API_KEY=sk-xxxxxxxx
LLM_BASE_URL=https://api.deepseek.com/v1
LLM_TEMPERATURE=0.7
LLM_MAX_TOKENS=4096
LLM_REASONING_EFFORT=high
LLM_THINKING=true

python-dotenv 负责把这些读进环境变量。我们放在 agent 包的 __init__.py 里,导入包时自动执行一次:

"""agent 包初始化:导入本包时自动加载 .env。

find_dotenv() 从本文件位置逐层向上查找 .env,自动定位项目根目录,
不依赖运行时的工作目录(cwd),比 load_dotenv(".env") 更稳。
override=False 表示已存在的真实环境变量优先于 .env。
"""

from dotenv import find_dotenv, load_dotenv

load_dotenv(find_dotenv(usecwd=False), override=False)
  • find_dotenv():从代码文件位置逐层向上找 .env,自动定位项目根,不怕换目录运行。
  • override=False:真实环境变量优先于 .env(方便部署时用系统环境变量覆盖)。

pydantic 负责把零散的环境变量变成一个有结构、带校验的配置对象 LLMConfig

class LLMConfig(BaseModel):
    """大模型相关配置。"""

    model_config = ConfigDict(protected_namespaces=())

    provider: str = Field(description="模型提供商,目前支持 deepseek")
    model: str = Field(description="模型名称")
    api_key: str = Field(description="API Key(必填)")
    base_url: str = Field(description="API 基础地址")
    temperature: float | None = Field(default=None, description="采样温度")
    max_tokens: int | None = Field(default=None, description="单次生成最大 token 数")
    reasoning_effort: str | None = Field(default=None, description="推理强度:high / medium / low")
    thinking: bool = Field(default=False, description="是否开启思考模式")

读取与校验(缺 LLM_API_KEY 直接报错,@lru_cache 保证只解析一次):

@lru_cache(maxsize=1)
def load_config() -> LLMConfig:
    """加载并缓存 LLM 配置。"""
    api_key = _get_str("LLM_API_KEY")
    if not api_key:
        raise ValueError("缺少必填配置 LLM_API_KEY,请在 .env 中设置")

给 Java 同学:pydantic.BaseModel ≈ 带校验的 DTO(@Data + Bean Validation),@lru_cache ≈ 单例缓存。

4.2 提示词层:jinja2

它解决什么问题:智能体的「人设/行为规范」很长,写死在代码里既难维护、又没法复用。于是把提示词放进 .md 模板,用 jinja2 渲染,还能塞变量(如 {{ domain }})。

加载器很薄:

def render_prompt(template_name: str, **context: object) -> str:
    """渲染指定名称的提示词模板。

    Args:
        template_name: md 目录下的模板文件名,例如 "system.md"。
        **context: 传给 Jinja2 模板的变量。
    """
    logger.info("加载提示词模板:%s,上下文:%s", template_name, context)
    template = _env.get_template(template_name)
    return template.render(**context).strip()

铁矿石专家的提示词模板 iron_ore_forecast.md 则规定了身份准则(必须自称铁矿石专家、不能自称通用助手)、7 大分析维度输出结构免责声明

4.3 模型层:langchain-deepseek

它解决什么问题:把「我们的配置」翻译成「能跟 DeepSeek API 对话的对象」。ChatDeepSeek 继承自 langchain 的 BaseChatOpenAI,用 OpenAI 兼容协议连接 DeepSeek。

def build_model() -> ChatDeepSeek:
    """根据配置文件构建 DeepSeek 聊天模型。"""
    cfg = load_config()
    kwargs: dict[str, object] = {
        "model": cfg.model,
        "api_key": cfg.api_key,
        "api_base": cfg.base_url,
    }
    if cfg.temperature is not None:
        kwargs["temperature"] = cfg.temperature
    if cfg.max_tokens is not None:
        kwargs["max_tokens"] = cfg.max_tokens
    if cfg.reasoning_effort:
        kwargs["reasoning_effort"] = cfg.reasoning_effort
    if cfg.thinking:
        kwargs["extra_body"] = {"thinking": {"type": "enabled"}}
    return ChatDeepSeek(**kwargs)

其中 reasoning_effortextra_body={"thinking": {"type": "enabled"}} 用于开启 deepseek-v4-pro推理/思考模式——模型会先在内部「想一想」再回答,质量更高。

4.4 组装层:deepagents

它解决什么问题:模型本身只会「聊天」,而智能体还需要工具(管理待办、读写文件、执行命令、调用子代理)和统一的系统提示词编排deepagentscreate_deep_agent 一行就把这些装好:

def build_agent():
    """构建使用 DeepSeek 的铁矿石价格预测 deep agent。"""
    system_prompt = load_iron_ore_forecast_prompt()
    return create_deep_agent(
        model=build_model(),
        system_prompt=system_prompt,
    )

create_deep_agent 默认会给智能体配上一套内置工具(write_todosread_file/write_file/edit_file/ls/glob/grepexecutetask),并把我们的 system_prompt 放到最前面、再拼上框架自带的基础提示词。


5. 跑起来

if __name__ == "__main__":
    result = agent.invoke(
        {"messages": [{"role": "user", "content": "你是谁"}]}
    )
    print(result["messages"][-1].content)
uv run python -m mystu.buildagent.agent.deepagent

输出(节选):

我是铁矿石价格预测专家……近期影响价格的核心因素:① 供给端发运节奏;② 中国铁水产量与政策;③ 港口库存方向;④ 钢厂利润;⑤ 宏观与汇率;⑥ 衍生品与资金情绪……⚠️ 以上不构成投资建议。


6. 踩坑记录(比成功更值钱)

  1. venv 没有 pip:用 uv 创建的虚拟环境默认不带 pip,先 python -m ensurepip --upgrade 才能装包。
  2. 系统提示词被「稀释」create_deep_agent 会在你的人设后面拼一大段「通用 deep agent」提示词,短人设会被压过去,模型甚至自称「Claude / 通用助手」。解决办法是把人设写(明确「身份最高优先级、绝不自称其它名字」)。
  3. load_dotenv(".env") 换目录就失效:相对路径是按当前工作目录(cwd)找的。改用 find_dotenv(usecwd=False),它按代码文件位置向上找,跟在哪运行无关。
  4. python -c "..." 测 dotenv 会误判find_dotenv 检测到 __main__ 没有 __file__ 时会当成「交互式」并退回用 cwd——所以要用真实脚本测试,而不是 -c

7. 小结

搭第一个智能体,本质是把这几块拼起来:

  • 配置(python-dotenv + pydantic):让参数可配、可校验;
  • 提示词(jinja2):定义智能体「是谁、怎么答」;
  • 模型(langchain-deepseek + DeepSeek):提供智能;
  • 框架(deepagents):补齐工具与编排,让它从「会聊天」变成「会做事」。

理解了每个组件的职责,后面无论是换模型、换领域,还是加工具,都只是「替换其中一块」而已。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐