我的第一个智能体(Agent):从零搭一个「铁矿石价格预测专家」
这是一篇实战记录:用 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_effort 和 extra_body={"thinking": {"type": "enabled"}} 用于开启 deepseek-v4-pro 的推理/思考模式——模型会先在内部「想一想」再回答,质量更高。
4.4 组装层:deepagents
它解决什么问题:模型本身只会「聊天」,而智能体还需要工具(管理待办、读写文件、执行命令、调用子代理)和统一的系统提示词编排。deepagents 的 create_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_todos、read_file/write_file/edit_file/ls/glob/grep、execute、task),并把我们的 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. 踩坑记录(比成功更值钱)
- venv 没有 pip:用
uv创建的虚拟环境默认不带 pip,先python -m ensurepip --upgrade才能装包。 - 系统提示词被「稀释」:
create_deep_agent会在你的人设后面拼一大段「通用 deep agent」提示词,短人设会被压过去,模型甚至自称「Claude / 通用助手」。解决办法是把人设写强(明确「身份最高优先级、绝不自称其它名字」)。 load_dotenv(".env")换目录就失效:相对路径是按当前工作目录(cwd)找的。改用find_dotenv(usecwd=False),它按代码文件位置向上找,跟在哪运行无关。python -c "..."测 dotenv 会误判:find_dotenv检测到__main__没有__file__时会当成「交互式」并退回用 cwd——所以要用真实脚本测试,而不是-c。
7. 小结
搭第一个智能体,本质是把这几块拼起来:
- 配置(python-dotenv + pydantic):让参数可配、可校验;
- 提示词(jinja2):定义智能体「是谁、怎么答」;
- 模型(langchain-deepseek + DeepSeek):提供智能;
- 框架(deepagents):补齐工具与编排,让它从「会聊天」变成「会做事」。
理解了每个组件的职责,后面无论是换模型、换领域,还是加工具,都只是「替换其中一块」而已。
更多推荐



所有评论(0)