How (and Why) I Built an AI Assistant
TL;DR · AI 摘要
作者自建AI助手以获得控制权、数据安全和深度理解,而非依赖现有工具。
核心要点
- 自建AI助手可确保数据不通过第三方平台,提升安全性。
- 自建工具能更好地适配个人工作流程和语境。
- 构建AI助手有助于深入理解其内部机制和问题排查。
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 自建AI助手的动机与过程
- 动机
- 控制权
- 数据安全
- 深度理解工具
- 构建过程
- 架构设计
- 代码实现
- 问题与改进
金句 / Highlights
值得收藏与分享的关键句。
The honest answer for me was control. Not in a paranoid, off-grid way, but in the practical sense that every off-the-shelf assistant is designed around someone else's assumptions about what you need.
Building your own means you decide where the data lives.
you understand a tool far better when you build it yourself.
我为何(以及如何)构建了一个AI助手 - KDnuggets
publ: 2026年6月17日
- 博客热门文章
- 主题 人工智能 职业建议 计算机视觉 数据工程 数据科学 语言模型 机器学习 MLOps NLP 编程 Python SQL
- 数据集
- 活动
- 资源 快速参考指南 推荐 技术简报
- 广告
加入通讯
#header end
/ad_wrapper
我为何(以及如何)构建了一个AI助手
本文诚实地描述了我为何选择构建一个自定义的AI助手而不是直接购买一个,它的架构是什么样的,实际的代码,哪些地方出了问题,以及现在我真正依赖它所做的一切。
作者:
Shittu Olumide,技术内容专家,2026年6月17日发布于人工智能领域
<div class="addthis_native_toolbox"></div>
# 引言
一切始于一个周二,那天我完全失控了。我有三个客户简报需要总结,一堆研究标签一直拖延着没处理,几封需要认真回复的电子邮件,还有一个技术文档已经写了半截,整整四天都开着一个标签。当我从所有这些任务之间切换注意力抬起头时,已经过了晚上7点,我几乎没有什么有意义的工作成果。
那天晚上,我没有关上笔记本电脑,认输,而是开始以不同的方式思考这个问题。我没有时间不足,而是缺乏杠杆。我当天做的每一个任务,其实都可以委托给比浏览器书签更聪明的东西。所以我开始构建。
本文诚实地描述了这个过程:为何我选择构建一个自定义的AI助手而不是直接购买一个,它的架构是什么样的,实际的代码,哪些地方出了问题,以及现在我真正依赖它所做的一切。
# “为什么”在“如何”之前
大多数决定构建AI助手的人都会先搜索“Python LangChain教程”。这是倒着来。第一个值得深思的问题是:既然Siri、ChatGPT、Copilot以及其他十几个工具已经存在,为什么还要构建一个?
对我而言,诚实的答案是控制。不是那种偏执、离线的控制,而是从实际意义上讲,每个现成的助手都是基于其他人对你需求的假设而设计的。它们是通用型的,而通用型意味着妥协。我想要一个了解我的上下文、使用我的语气、连接到我的特定工具,并且保持在一个我已经信任的工作流程中的助手。
还有一个数据问题。当你使用第三方助手时,你的提示和上下文会通过他们的基础设施。对于个人生产力来说,这可能可以接受。但对于任何与客户相关或商业敏感的内容,情况就变得模糊了。构建你自己的助手意味着你可以决定数据存储的位置。
还有学习曲线的论点,我认为这被低估了:当你自己构建一个工具时,你会更了解它。当出现问题时,你知道该在哪里查找。当你想要它做新的事情时,你不需要等待产品更新。
时间安排也使得这个决定更容易被证明是合理的。根据MarketsandMarkets的报告,AI助手市场预计从2025年的33.5亿美元增长到2030年的211.1亿美元——年复合增长率达44.5%。这种增长轨迹告诉你,这项技术不是一种趋势,而是基础设施。现在通过构建而不是仅仅消费来掌握它,将使你在两年后领先于大多数人。
话虽如此,构建并不总是最佳选择。如果你需要一个快速回答引擎或每月花费20美元的写作辅助工具,直接购买即可。但如果你想要一个能够与你的实际工作流程集成、从你的偏好中学习,并以符合你工作方式的方式处理任务的工具,那么构建它就是值得的。
# 选择技术栈
一旦我决定构建,下一步就是决定使用什么技术来构建。以下是我实际考虑的内容,而不是一个通用的对比图表。
- 在选择LLM时,我有两个严肃的选项:OpenAI的GPT-4o和Anthropic的Claude。我在研究、写作和推理任务中使用相同的提示对两者进行了测试。GPT-4o速度快且功能广泛,拥有成熟的API。Claude在处理长文档和细致的指令遵循方面表现尤为出色。最终,我选择了GPT-4o作为主要模型,因为它的工具调用可靠性以及其生态系统的成熟度,而Claude则作为某些文档密集型任务的备用选项。
- 在编排方面,我选择了LangChain。在开发者圈子中,关于LangChain是否添加了过多的抽象存在相当多的讨论,这种批评并非毫无道理。但对于像这样的项目——需要记忆、工具使用和推理循环的项目——LangChain的抽象确实节省了大量时间。另一种选择是自己编写这些底层代码,虽然你可以这么做,但当你试图发布一个功能性的产品时,这不是你注意力的最佳去向。
- 从第一天起,记忆就是一个必要条件。一个无状态的聊天机器人在会话之间会忘记一切,对于一次性问题是有用的。但它对于一个真正的助手来说是无用的。LangChain的ConversationBufferMemory在会话内的上下文中表现良好。为了跨会话的持久性,我使用了一个简单的SQLite支持的方法,这将在代码部分中展示。
- 在工具方面,我赋予了助手搜索网络(通过DuckDuckGo的API——无需密钥)、阅读并总结我传给它的文件,以及调用我为特定重复任务编写的自定义Python函数的能力。这就是真正的价值所在:将它从一个聊天机器人转变为一个能够真正执行任务的工具。
一个清晰的水平架构流程图展示了技术栈
# 设置环境
在任何代码运行之前,你需要三个东西:Python 3.10或更高版本、一个虚拟环境,以及安全存储的API密钥。
步骤1:创建和激活虚拟环境
# 创建一个名为 'assistant_env' 的虚拟环境
python -m venv assistant_env
# 在 macOS/Linux 上激活它
source assistant_env/bin/activate
# 在 Windows 上激活它
assistant_env\Scripts\activate虚拟环境将你的项目的依赖项与其他一切隔离开来。这比听起来更重要——项目之间的依赖项冲突是常见且无声的错误来源。
步骤2:安装所需的包
pip install langchain==0.3.0 \
langchain-openai \
langchain-community \
langgraph \
duckduckgo-search \
python-dotenv \
pydantic \
requests每个包的作用如下:
- langchain 是连接你的LLM、记忆和工具的核心框架。
- langchain-openai 是用于OpenAI模型的特定连接器。
- langchain-community 提供了对社区构建的工具和集成的访问,包括DuckDuckGo搜索。
- langgraph 能够处理更复杂、有状态的代理工作流程。
- duckduckgo-search 使助手能够在不需要 API 密钥的情况下搜索网络。
- python-dotenv 从 .env 文件中加载你的 API 密钥,而不是硬编码它们。
- pydantic 处理结构化输入和输出的数据验证。
步骤 3:安全地存储你的 API 密钥
永远不要将 API 密钥直接硬编码到你的脚本中。在项目根目录中创建一个 .env 文件:
# .env 文件 -- 请勿将其提交到版本控制中
OPENAI_API_KEY=your_openai_key_here然后立即将 .env 添加到你的 .gitignore 文件中:
# .gitignore
.env
assistant_env/
__pycache__/# 构建核心助手
这就是所有内容汇聚的地方。我将按照构建顺序逐步介绍每个组件。
- 连接到 LLM # assistant.py import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI # 从 .env 文件加载环境变量 load_dotenv() # 初始化语言模型 # 温度控制随机性:0 = 专注且确定,1 = 更具创意 # 对于需要准确且一致的助手,保持这个值较低(0.1 到 0.3) llm = ChatOpenAI( model="gpt-4o", temperature=0.2, api_key=os.getenv("OPENAI_API_KEY") ) 这做了什么:ChatOpenAI 通过 API 连接到 GPT-4o。温度参数值得理解:在 0 时,模型总是选择最可能的下一个标记,这会产生非常一致但有时僵化的输出。在 1 时,它会更加多样化和有创意。对于任务导向的助手,保持在 0.1 到 0.3 之间可以保证可靠性,同时不会失去自然语言的质量。
- 设计系统提示 系统提示是整个构建中最被低估的部分。它定义了助手的性格、约束条件以及如何处理模糊的情况。花比你想象的更多时间在这里。 # 系统提示作为助手的标准指令。 # 它在每次对话开始时发送,以锚定其行为。 SYSTEM_PROMPT = """ 你是一个专注、可靠的个人助手。你的工作是帮助用户研究主题、总结文档、起草书面内容并处理结构化任务。你总是: - 在详细说明之前直接回答 - 当不确定时说明,而不是猜测 - 如果任务确实模糊,请求澄清 - 除非明确要求,否则保持回答简洁 你有网络搜索的访问权限,并可以阅读用户提供的文件。使用这些工具时,始终引用信息来源。不要编造事实、发明引用或用听起来合理的虚构内容填补空白。 """ 这做了什么:这个提示在每次对话之前发送。把它想象成你给一个新入职的人类助手的第一天的工作描述。它越具体,你就越不需要在对话过程中纠正模型。模糊的指令总是会产生模糊的行为。
- 添加内存
没有内存,当您开始一条新消息时,您的助手会忘记所有内容。这是解决方法:
from langchain.memory import ConversationBufferMemory
from langchain_community.chat_message_histories import SQLChatMessageHistorySQLChatMessageHistory 将对话历史存储在本地 SQLite 数据库中。
session_id 允许您维护独立的内存线程(例如每个项目一个)。
message_history = SQLChatMessageHistory(
session_id="main_session",
connection_string="sqlite:///assistant_memory.db"
)以便模型知道之前说了什么。
memory = ConversationBufferMemory(
memory_key="chat_history",
chat_memory=message_history,
return_messages=True
)此操作的作用: SQLChatMessageHistory 将每次对话保存到名为 assistant_memory.db 的本地 SQLite 文件中。这意味着您的助手可以在会话之间记住上下文。session_id 只是一个标签 —— 您可以为不同的项目或主题创建多个会话,它们彼此之间完全独立。 一个需要注意的问题是:缓冲内存存储完整的对话历史,长时间的对话最终会达到模型的上下文限制。对于生产环境使用,ConversationSummaryMemory 是更好的选择 —— 它将较旧的对话历史压缩成摘要,从而在令牌限制内保持对话内容。
- 赋予工具能力 这是聊天机器人与助手之间的关键区别。工具使模型能够执行实际操作。
from langchain.agents import AgentExecutor, create_openai_tools_agent from langchain_community.tools import DuckDuckGoSearchRun from langchain.tools import tool from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
工具 1:通过 DuckDuckGo 进行网络搜索,无需 API 密钥
search_tool = DuckDuckGoSearchRun()
工具 2:你自己定义的自定义文件阅读器
@tool 装饰器将这个函数注册为代理可以调用的内容
@tool def read_file(file_path: str) -> str: """从给定路径读取文本文件并返回其内容。当用户要求你读取、分析或总结文件时使用此工具。""" try: with open(file_path, "r", encoding="utf-8") as f: return f.read() except FileNotFoundError: return f"文件未找到:{file_path}" except Exception as e: return f"读取文件时出错:{str(e)}"
注册代理可以使用的工具
tools = [search_tool, read_file]
构建提示模板
MessagesPlaceholder 插入到内存(聊天历史)和代理的草稿板中
prompt = ChatPromptTemplate.from_messages([ ("system", SYSTEM_PROMPT), MessagesPlaceholder(variable_name="chat_history"), ("human", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad") ])
创建代理 —— 这将 LLM、工具和提示结合在一起
agent = create_openai_tools_agent(llm, tools, prompt)
AgentExecutor 是运行循环:它调用代理,运行代理选择的任何工具,
将结果反馈回来,并重复直到得到最终答案
agent_executor = AgentExecutor( agent=agent, tools=tools, memory=memory, verbose=True, # 生产环境中应设为 False;True 可以查看推理步骤 max_iterations=5 # 防止代理在困难问题上循环运行 )
这是它的工作方式:AgentExecutor 是引擎。当你向它发送消息时,它不会仅仅将消息传递给 LLM 并返回任何结果。它运行一个循环:模型决定是否需要使用工具,如果需要,调用工具,获取结果,思考下一步该怎么做,只有在满意时才返回最终答案。这是 ReAct(推理 + 行动)模式的实际应用。
一个圆形循环图,显示四个带有箭头连接的标记阶段
将所有内容组合在一起,主运行循环如下:
def chat(user_input: str) -> str:
"""
向助手发送消息并获取响应。
内存由 agent_executor 自动处理。
"""
try:
response = agent_executor.invoke({"input": user_input})
return response["output"]
except Exception as e:
# 优雅的错误处理 —— 告诉你哪里出错了,而不会导致会话崩溃
return f"出错了:{str(e)}。请重试或重新表述你的请求。"
# 运行助手的简单命令行界面
if __name__ == "__main__":
print("助手已准备就绪。输入 'quit' 退出。\n")
while True:
user_input = input("你: ").strip()
if user_input.lower() in ["quit", "exit", "q"]:
print("再见。")
break
if not user_input:
continue
response = chat(user_input)
print(f"\n助手: {response}\n")这有什么作用:chat() 函数是整个系统的唯一入口点。你传给它一个字符串,它会处理一切内容 —— 包括记忆检索、工具调用、LLM 推理和错误处理 —— 然后返回一个字符串。if __name__ == "__main__" 代码块将整个脚本转换为一个可以立即通过 python assistant.py 运行的命令行助手。
# 测试和修复问题
第一次运行这个脚本时,助手给出了一个自信但事实错误的回答,忽略了本应使用的工具,并以一种我非常讨厌的方式格式化了响应。这些情况并不罕见。这并不是构建失败的标志,而是真正工作的开始。
早期测试最重要的事情是验证代理是否在应该使用工具时确实使用了它们。一种常见的失败模式是模型在应该搜索时尝试从记忆中回答,因为系统提示没有明确表达期望。我通过向系统提示中添加以下内容来解决这个问题:
当回答有关近期事件、当前数据或任何时间敏感的问题时,始终使用网络搜索工具。不要依赖训练知识来回答可能已经改变的事实。关于错误处理:这比大多数教程所提到的更重要。根据 Mordor Intelligence 的研究,近一半的人工智能生成的代码在首次安全审查中失败。同样的原则也适用于人工智能生成的响应 —— 在你确信某种任务类型之前,输出应被视为草稿,而不是最终答案。上面 chat() 函数中的 try/except 代码块只是一个开始,随着你发现助手失败的具体方式,你将想要扩展它。
为了进行更结构化的测试,可以编写如下测试用例:
# test_assistant.py
# 运行这些测试用例,以验证在部署之前助手的行为是否符合预期
test_cases = [
{
"input": "What is the current price of Bitcoin?",
"expected_behavior": "Should use web search, not answer from memory"
},
{
"input": "Summarize the file at /tmp/test_document.txt",
"expected_behavior": "Should call the read_file tool"
},
{
"input": "What did I ask you five messages ago?",
"expected_behavior": "Should reference conversation memory correctly"
}
]
for case in test_cases:
print(f"Testing: {case['input']}")
print(f"Expected: {case['expected_behavior']}")
result = chat(case["input"])
print(f"Got: {result[:200]}...") # 打印响应的前 200 个字符
print("---")在每次修改系统提示或工具配置后运行这些测试用例。小的提示修改通常会产生意想不到的后续影响。
# 完整代码
以上内容已经分部分进行了说明。这里是完整的、可以直接复制粘贴的文件。将其保存为 assistant.py,确保 .env 文件在同一个目录中,并通过 python assistant.py 运行它。
# assistant.py
# 完整的 AI 助手 -- 使用 LangChain、GPT-4o、DuckDuckGo 搜索和 SQLite 内存构建
# 依赖: Python 3.10+ | 安装: pip install langchain==0.3.0 langchain-openai
# langchain-community langgraph duckduckgo-search python-dotenv pydantic requests
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_community.tools import DuckDuckGoSearchRun
from langchain.tools import tool
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# ──────────────────────────────────────────────
# 1. 加载环境变量
# ──────────────────────────────────────────────
# 从你的 .env 文件中读取 OPENAI_API_KEY。
# 不要在源代码中直接硬编码 API 密钥。
load_dotenv()
# ──────────────────────────────────────────────
# 2. 初始化语言模型
# ──────────────────────────────────────────────
# temperature=0.2 使响应保持专注和一致。
# 如果你想要更富有创意和多样的输出,可以将其提高到接近 1.0。
llm = ChatOpenAI(
model="gpt-4o",
temperature=0.2,
api_key=os.getenv("OPENAI_API_KEY")
)
# ──────────────────────────────────────────────
# 3. 定义系统提示
# ──────────────────────────────────────────────
# 这是助手的标准指令集。
# 它影响助手在每次交互中的行为 -- 请谨慎处理。
SYSTEM_PROMPT = """
你是一个专注、可靠的个人助手。
你的职责是帮助用户研究主题、总结文档、
撰写书面内容并处理结构化任务。你总是:
- 在详细说明之前直接回答问题
- 当不确定时说明,而不是猜测
- 如果任务确实模糊,请求澄清
- 保持回答简洁,除非明确要求详细信息
你有访问网络搜索的功能,并可以阅读用户提供的文件。
使用这些工具时,始终要引用信息来源。
当回答有关最近事件、当前数据或任何
时间敏感的问题时,始终使用网络搜索工具。不要依赖你的
训练知识来提供可能已经改变的事实。
不要编造事实、发明引用或用听起来合理的虚构内容填补空白。
"""
# ──────────────────────────────────────────────
# 4. 设置持久化内存
# ──────────────────────────────────────────────
# SQLChatMessageHistory 将对话历史记录保存到本地 SQLite 数据库中。
# 这意味着助手可以记住跨会话的上下文,而不仅仅是在一个会话中。
# 更改 session_id 以保持独立的内存线程(例如每个项目一个)。
message_history = SQLChatMessageHistory(
session_id="main_session",
connection_string="sqlite:///assistant_memory.db"
)
# 每个提示中,使模型始终具有完整的对话上下文。
# 注意:对于非常长的对话,将其替换为 ConversationSummaryMemory
# 以避免达到模型的令牌限制。
memory = ConversationBufferMemory(
memory_key="chat_history",
chat_memory=message_history,
return_messages=True
)
# ──────────────────────────────────────────────
# 5. 定义工具──────────────────────────────────────────────
工具 1:通过 DuckDuckGo 进行网络搜索 -- 不需要 API 密钥
search_tool = DuckDuckGoSearchRun()
工具 2:自定义文件读取器
@tool 装饰器将这个 Python 函数注册为一个可调用工具
当用户要求代理读取文件时,代理可以调用这个工具。
@tool def read_file(file_path: str) -> str: """ 从给定路径读取文本文件并返回其内容。 当用户要求你读取、分析或总结文件时使用此工具。 """ try: with open(file_path, "r", encoding="utf-8") as f: return f.read() except FileNotFoundError: return f"文件未找到: {file_path}" except Exception as e: return f"读取文件时出错: {str(e)}"
将所有工具收集到一个列表中 -- 在构建更多自定义工具时,将它们添加到这里
tools = [search_tool, read_file]
──────────────────────────────────────────────
6. 构建代理
──────────────────────────────────────────────
提示模板结构化了发送给模型的每条消息。
每次轮次中,MessagesPlaceholder 插入到内存(chat_history)和代理的
内部推理草稿中。
prompt = ChatPromptTemplate.from_messages([ ("system", SYSTEM_PROMPT), MessagesPlaceholder(variable_name="chat_history"), ("human", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad") ])
create_openai_tools_agent 将 LLM、工具和提示连接在一起
形成一个单一的代理,该代理知道何时以及调用哪些工具。
agent = create_openai_tools_agent(llm, tools, prompt)
AgentExecutor 是运行时循环。
它调用代理,运行代理选择的任何工具,将这些结果反馈回去,
并重复直到达到最终答案(或达到最大迭代次数)。
agent_executor = AgentExecutor( agent=agent, tools=tools, memory=memory, verbose=True, # 生产环境中设置为 False;True 显示推理步骤 max_iterations=5 # 防止代理在困难问题上无限循环 )
──────────────────────────────────────────────
7. 聊天函数
──────────────────────────────────────────────
def chat(user_input: str) -> str: """ 向助手发送一条消息并获取响应。 内存、工具使用和错误处理都在这里进行管理。 """ try: response = agent_executor.invoke({"input": user_input}) return response["output"] except Exception as e:
返回可读的错误信息而不是崩溃会话。
在生产环境中,此处记录完整的异常以进行调试。
return f"发生了一些错误: {str(e)}。请重试或重新表述您的请求。"
──────────────────────────────────────────────
8. 命令行界面
──────────────────────────────────────────────
直接运行此文件以启动命令行会话: python assistant.py
如果要将其包装在浏览器 UI 中,参见第 7 部分中的 Gradio 或 Streamlit。
if __name__ == "__main__": print("助手已准备就绪。输入 'quit' 退出。\n") while True: user_input = input("You: ").strip() if user_input.lower() in ["quit", "exit", "q"]: print("再见。") break if not user_input: continue response = chat(user_input) print(f"\nAssistant: {response}\n")
#### // 结果
使用大约两周后,现在助理实际上为我处理了以下工作:
- 研究整合:我给它一个主题和三到五个网址,它会提取关键点并生成结构化摘要。过去需要45分钟的工作,现在只需约4分钟,剩下的时间用于我自己的验证和判断。
- 草稿生成:电子邮件、摘要和结构化文档的初稿。输出内容并非最终版本,我也不期望它是最终版本,但有内容可供编辑,比每次从空白页面开始更快。
- 文件处理:我把会议记录、PDF和日志文件放入一个文件夹,然后让它提取特定信息。只要文件是文本格式且字数在5万字以内,它处理起来非常可靠。
节省的时间是真实的。DX对135,000多名开发者的分析数据显示,使用AI工具每周平均节省3.6小时,而每日用户节省的时间更多。对于任务繁重的几天,我的体验与这个数据范围相符,但具体节省时间因工作类型而异。
# 总结
我一开始描述的那个周二——那天我工作了一整天却几乎没有产出——仍然会发生。但这种情况变少了,而且即使发生,也很少是因为我陷入错误类型的工作中。助理处理那些不需要我亲自完成的工作部分,这让我可以花更多时间在需要我亲自处理的部分。
我没有预料到的是,构建这个工具改变了我对工作的看法。当你负责一个工具时,你会开始以不同的方式注意到摩擦点。你会更频繁地问自己“这是否可以委托他人完成?”,这种思维方式无论是否使用AI都是一种有用的习惯。
构建这样一个工具的门槛比看起来要低得多。上面完整的助理程序不到150行Python代码,使用的是自由可用的框架,可以在任何安装了Python的机器上运行。最难的部分是决定你真正希望它做什么——这个问题值得认真回答,因为专注的助理总是胜过通用的助理。
从小处着手。让它只做一件事。只有在简单级别上没有更多价值可提取时,再增加复杂性。这种方法适用于工具,也适用于围绕它们建立习惯。
Shittu Olumide 是一名软件工程师和技术作家,热衷于利用前沿技术创作引人入胜的故事,注重细节,擅长将复杂概念简化。你也可以在 Twitter 上找到 Shittu。
更多相关内容
- 8 个内置的 Python 装饰器,用于编写优雅的代码
- 每位数据工程师都应该了解的 10 个内置 Python 模块
- 掌握 Python 内置的统计模块:完整指南
- 7 个看似玩笑的 Python 内置函数(直到你使用它们)
- 我如何构建一个 MCP 来自动化我的数据科学工作
- 我如何使用一个混乱的 DoorDash 数据集构建一个数据清洗管道
<hr class="grey-line"><br> <div><h3>我们推荐的 5 门免费课程</h3><br> </div>
Mailchimp for WordPress v4.13.0 - https://wordpress.org/plugins/mailchimp-for-wp/
/ Mailchimp for WordPress 插件
你可以从这里开始编辑。
如果评论已关闭。
<= 上一篇
下一篇 =>
#content end
<script type="text/javascript">kda_sid_write(kda_sid_n);</script>
最新文章
- 为新手解释损失函数(模型如何知道自己是错误的)实用 SQL 技巧每位数据科学家都应该知道 Python 字典技巧和窍门你应该始终记住 高级连接技术:LATERAL 连接、半连接、反连接 我如何(以及为什么)构建了一个 AI 助手 使用 OpenAI Codex 的 5 个有趣项目
热门文章
- 将 Claude Code 与本地模型配对
- 为你的创业点子获得资金的 7 个最佳方式
- 2026 年成为 LLM 工程师的路线图
- 廉价本地代理编程:Claude Code + Ollama + Gemma4
- Anthropic 完整指南:Claude 技能构建
- 我如何(以及为什么)构建了一个 AI 助手
- 使用 Python 中的 sktime 构建时间序列机器学习模型
- 使用 OpenAI Codex 的 5 个有趣项目
- 停止在 Pandas 中编写循环:尝试 7 个更快的替代方法
- 5 个有用的 Python 脚本用于自动化无聊的 PDF 任务
#content_wrapper end
© 2026
Guiding Tech Media
|
关于
联系我们
广告
隐私
服务条款
2026 年 6 月 17 日由 Olumide Shittu 发布
blank
不,谢谢!
/.main_wrapper
<script defer type="text/javascript" src="https://s7.addthis.com/js/300/addthis_widget.js#pubid=gpsaddthis"></script>
noptimize
/noptimize