今年很多开发者的卡点,已经不是“大模型能做什么”,而是“我明明调通了 API,却依然不知道怎么把它变成真正的应用”。看了一大堆框架文档,收藏了十几个教程,最后面对一个最简单的知识库问答需求,还是不知道从哪里下手。
这个问题的根源,不是缺少学习资料,而是缺少一条“能亲手跑完”的路径。datawhalechina/happy-llm 这个开源项目,恰好就是为这条路径设计的。它把大模型应用开发拆成一个个很小的节点,每个节点都用最小可运行代码去验证一个知识点,从“一句话调用大模型”开始,一直走到 RAG、Agent、Web 应用部署。
这篇文章不打算简单复述项目介绍,而是想结合 LLM 应用开发的实际痛点,把 happy-llm 背后的设计思路、技术链路、环境准备、代码实现和常见坑位完整梳理一遍。如果你正在学 LLM 应用开发,或者准备用大模型 API 搭一个真实项目,这篇文章应该能帮你少走很多弯路。
1. 为什么 LLM 应用开发总卡在“入门到放弃”
先聊一个现象。很多开发者掌握 Python,也了解神经网络的基本概念,但一进入大模型应用开发领域,就陷入三种典型困境。
第一种困境是“只逛概念,不动手”。Token、temperature、system prompt、上下文窗口、微调、RAG、Agent,每个词都听过,每个词都能聊两句,但真要写代码时,脑子里只有一个client.chat.completions.create,后面怎么做完全没概念。
第二种困境是“一上来就上框架”。看到 LangChain、LlamaIndex 很火,直接去读框架文档,结果被 Chain、Agent、Tool、Memory 这些抽象概念绕晕。框架本身没有错,但框架是给已经理解底层逻辑的人用的加速器。如果你还没亲手调通过一次原生 API,没有自己拼过 messages 数组,没有手动处理过一次工具调用返回,框架只会变成另一层黑盒。
第三种困境是“代码能跑,但只会跑”。网上可以找到大量现成的 DEMO,复制下来确实能运行,但换一个需求就不知道怎么改。这说明没有理解 API 请求和响应结构背后的工程逻辑:上下文是状态、输出格式是契约、工具调用是循环、检索质量决定回答质量。
LLM 应用和传统软件开发的本质区别在于:传统接口的输入输出是确定的结构,而大模型 API 的输出是概率性的文本。这意味着你必须额外做结构化约束、上下文管理、结果校验和异常兜底。这一整套方法,不是背概念能学会的,必须通过一个接一个的最小可运行代码去练习。happy-llm 的意义,就是帮你把这条路走通。
2. HAPPY-LLM 是什么:项目定位与设计理念
happy-llm 是 Datawhale 社区维护的一个开源学习项目。Datawhale 在国内开源社区里比较特殊,它不只是发代码,还会组织大家一起学,强调“开源学习”这件事本身。这个项目的名字里就带着学习理念:保持一个自己能坚持的节奏,用小的正向反馈把学习持续下去,而不是一次性吞下全部知识。
从项目设计来看,它没有追求大而全的理论覆盖,而是把目标设定得非常明确:让一个有一定 Python 基础的开发者,通过一段不长的学习时间,亲手跑完一条完整的 LLM 应用开发主链路。从调 API 开始,到提示词设计、结构化输出、流式输出、多轮对话、RAG 检索增强生成、Agent 工具调用,最后落在一个可交互的 Web Demo 上。
这里要做一个重要区分:happy-llm 不是生产级框架,不是 LangChain 的替代品,也不是一个大模型推理引擎。你学完它,不会得到一个可以直接上生产的高并发服务,但你会得到比读十篇科普文章更扎实的东西——对 LLM 应用开发主链路每个环节的真实体感。
用一个表格来对比它和传统学习方式、框架文档的区别:
| 对比维度 | 传统理论教程 | 直接啃框架文档 | happy-llm 这类实战学习项目 |
|---|---|---|---|
| 学习起点 | 从注意力机制讲起 | 从框架抽象概念讲起 | 从一行能跑的 API 调用讲起 |
| 代码量 | 少,以原理图为主 | 多,但零散 | 每个节点一个完整小程序 |
| 反馈速度 | 慢,学完未必会写 | 慢,概念太多 | 快,每跑通一个都有正反馈 |
| 最终目标 | 理解原理 | 使用框架 | 理解主链路并具备扩展基础 |
| 适合人群 | 想深入原理的研究者 | 已有应用经验的开发者 | 刚入门应用开发的工程师 |
这个定位非常关键。如果你现在最需要的是快速建立对 LLM 应用开发的整体认知,并且希望每一步都有代码可跑,那么这类项目比纯理论书和纯框架文档都更适合你。
3. LLM 应用开发的核心概念:先分清几个关键词
在动手之前,先花一点时间把后面代码里会反复出现的几个概念讲清楚。这些词不是拿来背的,是拿来对应代码的。
第一个是 Chat Completion API。这是目前大模型应用开发最常用的接口形态。你传一个消息列表给它,它返回模型生成的文本。消息列表里每条消息都带角色,通常有 system、user、assistant 三种。system 负责设定模型身份和行为边界,user 是用户输入,assistant 是模型历史回复。多轮对话就是不断往这个列表里追加消息。
第二个是 Token。Token 是模型处理文本的最小单位,可以粗略理解成“模型眼里的单词碎片”。文本长度、上下文窗口、计费,都以 Token 计算。你传入的 messages 和模型输出的完整内容,总 Token 数不能超过模型的上下文窗口。
第三个是 Temperature。它控制输出的随机性,数值越高回答越发散,越低越确定。需要稳定解析结果时,通常设置低一些;需要创意文案时,可以设高一些。
第四个是 Embedding。它把一段文本转换成一个高维向量,让语义相近的文本在向量空间里距离更近。RAG 里的“检索”,本质上就是计算用户问题和文档向量之间的相似度。
第五个是 Function Calling。它是让模型具备“行动能力”的关键。你在请求里声明一个函数的结构,模型判断需要调用它时,会返回结构化的调用参数,由你的代码真正执行函数,再把结果回传给模型生成最终回答。
第六个是 RAG。RAG 全称是 Retrieval-Augmented Generation,检索增强生成。核心思路是:外部文档切分成块,向量化后存入向量数据库,用户提问时先检索相关内容,再把检索结果和问题一起拼进提示词,让模型基于参考材料回答。它解决的是模型不懂私有知识、知识更新成本高、容易产生幻觉的问题。
这些概念可以用一张表映射到传统软件工程:
| LLM 应用概念 | 传统软件类比 | 核心作用 |
|---|---|---|
| system prompt | 配置中心 | 控制行为边界 |
| messages | 请求参数 | 传递会话上下文 |
| 结构化输出 | 接口契约 | 让文本可被程序解析 |
| Embedding | 索引 | 把文本变成可计算语义距离的数据 |
| Function Calling | API 网关 | 让模型触发真实操作 |
| RAG | 外部数据源查询 | 补充模型不知道的知识 |
理解了这些映射,后面写代码时就不会觉得大模型应用开发是另一套完全陌生的东西。它就是“模型做语义理解和决策,代码做确定性的计算和 IO”的结合。
4. 环境准备与前置条件
开始写代码之前,先把环境准备好。这里不会写死版本号,因为不同时间安装的依赖版本会有差异,但整体思路是通用的。
建议使用 Python 3.9 或更高版本。如果你本地已经装了 Anaconda 或者 Miniconda,可以直接用 conda 创建一个新环境;如果习惯用原生 Python,用 venv 也足够。
python -m venv llm_env source llm_env/bin/activate # Windows 下使用 llm_env\Scripts\activate然后安装核心依赖。这里以 OpenAI SDK 为例,国内很多大模型服务商都提供 OpenAI 兼容接口,所以代码结构可以复用,只需要换 base_url 和 api_key。
pip install openai python-dotenv numpy gradio把这些依赖写入requirements.txt也方便后续重建环境:
openai>=1.0 python-dotenv>=1.0 numpy>=1.24 gradio>=4.0接下来需要准备一个 API Key。如果你是第一次接触,优先使用你所在环境容易访问的模型服务商。注册后创建 Key,填入项目根目录下的.env文件:
LLM_API_KEY=你的_API_KEY LLM_BASE_URL=https://api.openai.com/v1 LLM_MODEL=gpt-4o-mini EMBEDDING_MODEL=text-embedding-ada-002这里强烈建议使用.env文件管理密钥,而不是把 Key 硬编码在代码里。后续涉及任何分享代码的场景,硬编码 Key 都是高危行为。
代码结构上,建议按主题分成独立文件,不要把所有代码堆在一个脚本里。参考结构如下:
happy-llm-practice/ ├── .env ├── requirements.txt ├── 01_basic_chat.py ├── 02_structured_output.py ├── 03_stream_chat.py ├── 04_rag_demo.py ├── 05_agent_demo.py └── 06_web_demo.py每个文件都是一个可以独立运行的最小示例,这本身就是 happy-llm 提倡的学习方式:一次只关注一个知识点,跑通了再进下一个。
5. 第一个 LLM 小程序:API 调用与基础对话
从最简单的程序开始。新建01_basic_chat.py,先实现一次最基本的对话补全。
# 文件路径:happy-llm-practice/01_basic_chat.py import os from openai import OpenAI from dotenv import load_dotenv load_dotenv() client = OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL") ) response = client.chat.completions.create( model=os.getenv("LLM_MODEL"), messages=[ {"role": "system", "content": "你是一个乐于助人的中文助手。"}, {"role": "user", "content": "用一句话解释什么是大语言模型。"} ], temperature=0.7 ) print(response.choices[0].message.content)这段代码做了三件事:读取环境变量、初始化客户端、发起一次对话请求。
注意messages是一个列表,列表里是消息字典。system 消息用来约束模型行为,user 消息是用户提问。这是 LLM 应用开发最核心的数据结构,后面所有复杂功能,本质上都在围绕这个列表做文章。
运行方式很简单:
python 01_basic_chat.py如果一切正常,会看到一行模型生成的回答。如果出现401,说明 API Key 不正确;如果出现超时,检查 base_url 和网络连通性;如果提示模型不存在,检查环境变量里的模型名是否和服务商提供的一致。
这里有一个初学者容易忽略的点:response是一个结构化的响应对象,不是纯文本。.choices[0].message.content才是最终文字内容。理解和熟悉这个响应结构,比背文档更有用,因为后面做流式输出、工具调用时,都要操作这个结构。
6. 工程化第一步:结构化输出、流式输出与多轮对话
能完成一次基础对话之后,接下来要把“模型聊天能力”工程化。这就要处理三个问题:模型输出怎么被程序稳定解析,用户体验怎么更流畅,多轮对话的上下文怎么管理。
6.1 结构化输出
模型返回的是自然语言文本,但程序需要的是 JSON、字典、列表这类结构。比如做一个信息抽取功能,你希望模型返回“公司名、金额、日期”,而不是一段口语化描述。
解决方案就是结构化输出。现在不少模型服务商支持response_format参数:
# 文件路径:happy-llm-practice/02_structured_output.py import os import json from openai import OpenAI from dotenv import load_dotenv load_dotenv() client = OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL") ) response = client.chat.completions.create( model=os.getenv("LLM_MODEL"), response_format={"type": "json_object"}, messages=[ {"role": "system", "content": "你是信息抽取助手。只输出严格 JSON,不要输出任何解释。"}, {"role": "user", "content": "从这句话中抽取公司名称和融资金额:'北京某科技公司宣布完成 5000 万元 A 轮融资。' 输出格式:{\"company\": \"\", \"amount\": \"\"}"} ] ) content = response.choices[0].message.content data = json.loads(content) print(data["company"], data["amount"])这里真正容易踩坑的地方是:JSON 解析失败。模型偶尔会输出多行解释,或者把 JSON 包在代码块标记里。稳妥做法是在 system 消息里反复强调“只输出严格 JSON”,并在解析时加入异常处理,失败就重试一次。
如果服务商不支持response_format参数,退而求其次,也可以用强约束的提示词来引导输出格式,再配合正则或字符串清理来做兜底。
6.2 流式输出
普通请求要等模型把完整内容生成完才返回,体验上像卡顿。流式输出可以边生成边推送,让用户看到逐字出现的效果。代码改动很小:
# 文件路径:happy-llm-practice/03_stream_chat.py import os from openai import OpenAI from dotenv import load_dotenv load_dotenv() client = OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL") ) stream = client.chat.completions.create( model=os.getenv("LLM_MODEL"), messages=[{"role": "user", "content": "写一段关于学习大语言模型应用开发的 50 字总结。"}], stream=True ) for chunk in stream: delta = chunk.choices[0].delta if delta and delta.content: print(delta.content, end="", flush=True)对比普通请求,这里只是加了stream=True,然后遍历返回的 chunk。生产环境中,流式输出还要考虑客户端连接断开、超时中断等情况,但作为学习项目,跑通这个最小示例就足够了。
6.3 多轮对话与上下文管理
大模型 API 本身是无状态的。第二次请求时,模型不会记得第一次请求说了什么。所谓“多轮对话”,其实是把历史消息重新全部传给模型。
history = [ {"role": "system", "content": "你是一个中文助手。"} ] def chat_with_history(user_input): history.append({"role": "user", "content": user_input}) response = client.chat.completions.create( model=os.getenv("LLM_MODEL"), messages=history ) answer = response.choices[0].message.content history.append({"role": "assistant", "content": answer}) return answer每次对话,把整个history数组传给模型。这就是为什么上下文窗口很重要:历史越长,消耗的 Token 越多,早晚会超出模型的窗口限制。
工程上常用两种策略:一是滑动窗口,只保留最近 N 条消息;二是对历史做摘要,把早期对话压缩成一段概述。至于是不是需要引入专门的消息存储,取决于你的真实业务场景。
7. RAG 开发实战:让模型拥有私有知识
模型是在某个时间点训练完成的,它不知道你公司的内部文档、最新政策、私有产品手册。RAG 是目前解决这类问题的主流方案。它的核心流程是:外部文档切分成块 -> 每块文本做向量化 -> 向量存入向量数据库或索引 -> 用户提问时检索最相关的若干块 -> 把检索结果拼进提示词。
下面用一个最小示例演示完整链路。为了不引入额外框架,这里直接用 OpenAI 的 Embedding 接口和 NumPy 做相似度计算。生产环境请换成真正的向量数据库,但学习阶段跑通这个例子更重要。
# 文件路径:happy-llm-practice/04_rag_demo.py import os import numpy as np from openai import OpenAI from dotenv import load_dotenv load_dotenv() client = OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL") ) documents = [ "HAPPY-LLM 是由 Datawhale 社区维护的开源学习项目。", "它强调每天一小时,从最小可运行代码开始学习大模型应用开发。", "RAG 通过检索外部知识来增强模型的回答能力。", "Agent 通过 Function Calling 让模型具备调用外部工具的能力。", "多轮对话需要自行维护历史消息列表。" ] def get_embedding(text): resp = client.embeddings.create( model=os.getenv("EMBEDDING_MODEL"), input=[text] ) return resp.data[0].embedding doc_vectors = [get_embedding(doc) for doc in documents] def search(query, top_k=2): q_vec = get_embedding(query) scores = [] for i, doc_vec in enumerate(doc_vectors): score = float(np.dot(q_vec, doc_vec) / (np.linalg.norm(q_vec) * np.linalg.norm(doc_vec))) scores.append((score, i)) scores.sort(reverse=True) return [documents[i] for _, i in scores[:top_k]] query = "我该怎么学习大模型应用开发?" related = search(query) context = "\n".join(related) prompt = f"""请根据以下参考资料回答用户问题。 参考资料: {context} 用户问题:{query} 如果参考资料不足以回答问题,请直接说明。""" response = client.chat.completions.create( model=os.getenv("LLM_MODEL"), messages=[{"role": "user", "content": prompt}] ) print("检索到的资料:") for doc in related: print("-", doc) print("\n模型回答:") print(response.choices[0].message.content)这段代码的核心是search函数。它把用户问题转换成向量,和每条文档向量计算余弦相似度,取最相似的两条作为上下文。
真正影响 RAG 效果的关键点有两个。第一是文档切分。切得太碎,每块语义不完整;切得太大,检索出来噪音多,还浪费 Token。第二是检索质量。向量相似度并不总是语义相关,有时候需要加入重排环节。学习阶段先用最朴素的方案跑通,理解全流程,再逐步引入更高级的预处理和检索策略。
这个例子也解释了为什么 happy-llm 这类项目强调“最小可运行”:RAG 本身不是一个函数,而是一条数据链路。如果不从头到尾亲手走一遍,只靠读文档很难真正理解“切分 -> 向量化 -> 检索 -> 注入”之间的因果关系。
8. Agent 与 Function Calling:从“回答问题”到“执行任务”
对话能力和知识库问答,本质还是“回答问题”。但很多应用场景需要的是“执行任务”:用户问“北京今天天气怎么样”,你需要先去查天气接口,再组织语言回答。模型本身不联网、不执行代码,所以需要一套机制让模型调用外部工具。
这个机制就是 Function Calling。过程可以拆成四步:
第一步,在请求里声明工具函数的结构。第二步,模型判断需要调用工具时,返回一个tool_calls对象,包含函数名和参数。第三步,你的代码真正执行这个函数,拿到结果。第四步,把工具结果以role="tool"的消息追加到 messages,再让模型基于工具结果生成最终回答。
下面用一个查询天气的最小示例演示。真实项目中,函数体内部应该是请求天气服务 API,这里用本地返回模拟。
# 文件路径:happy-llm-practice/05_agent_demo.py import os import json from openai import OpenAI from dotenv import load_dotenv load_dotenv() client = OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL") ) def get_weather(city: str) -> str: # 实际项目中在这里调用天气服务 API return f"{city} 今天多云,气温 22 摄氏度。" tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的实时天气", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名,例如 北京" } }, "required": ["city"] } } } ] messages = [ {"role": "user", "content": "你好,请问北京今天天气怎么样?"} ] response = client.chat.completions.create( model=os.getenv("LLM_MODEL"), messages=messages, tools=tools, tool_choice="auto" ) msg = response.choices[0].message if msg.tool_calls: # 模型决定调用工具 call = msg.tool_calls[0] print("模型要求调用工具:", call.function.name) print("工具参数:", call.function.arguments) args = json.loads(call.function.arguments) result = get_weather(args["city"]) # 把工具结果回传给模型 messages.append(msg) messages.append({ "role": "tool", "tool_call_id": call.id, "content": result }) final_response = client.chat.completions.create( model=os.getenv("LLM_MODEL"), messages=messages, tools=tools ) print("最终回答:", final_response.choices[0].message.content) else: print("模型直接回答:", msg.content)这段代码体现了 Agent 的雏形:模型负责理解意图、决定调用哪个工具、生成传给工具的参数,外部代码负责真正执行。模型的角色更像是一个路由器或编排器,而不是答案的来源。
初学者最容易忽略的是工具结果的回传格式。tool_call_id必须和模型返回的call.id保持一致,否则模型无法把工具结果和之前的请求关联起来。
Agent 应用开发真正复杂的地方在于循环控制。一个任务可能需要连续调用多个工具,每次调用结果都会改变后续步骤。生产环境还需要考虑:设置最大工具调用轮数防止死循环,对工具输入做校验,对工具异常做兜底,确保工具权限最小化。所有这些,都是 Agent 从 Demo 走向可用的必经之路。
9. 用 Gradio 把脚本变成 Web 应用
学习到这里,你已经掌握了 API 调用、结构化输出、RAG、工具调用这几块能力,但都是在命令行里跑。要让一个非技术的同事或者朋友也能体验,可以做一个 Web 页面。Gradio 是目前非常方便的工具,几行代码就能搭出一个可交互界面。
# 文件路径:happy-llm-practice/06_web_demo.py import os import gradio as gr from openai import OpenAI from dotenv import load_dotenv load_dotenv() client = OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL") ) def chat(message, history): history = history or [] messages = [{"role": "system", "content": "你是一个友好的人工智能助手。"}] for user_msg, assistant_msg in history: messages.append({"role": "user", "content": user_msg}) messages.append({"role": "assistant", "content": assistant_msg}) messages.append({"role": "user", "content": message}) response = client.chat.completions.create( model=os.getenv("LLM_MODEL"), messages=messages ) return response.choices[0].message.content demo = gr.ChatInterface(fn=chat) demo.launch()运行:
python 06_web_demo.py终端会输出一个本地地址,通常是http://127.0.0.1:7860,浏览器打开即可开始对话。Gradio 的ChatInterface已经帮你处理了历史消息的展示和记录,你只需要关心如何调用模型。
这个阶段的重点不是把界面做得复杂,而是理解从纯函数到 Web 交互中间发生了什么:用户输入进入回调函数,函数调用模型,返回值渲染成界面消息。后面如果再接入前端的聊天组件、记忆持久化、用户鉴权,都是在同一套逻辑上扩展。
10. 常见问题与排查思路
把学习过程中最常遇到的问题整理成一张表,方便你快速定位。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
调用报401 | API Key 错误或失效 | 检查.env中 Key 是否正确 | 重新生成 Key,确认环境变量已加载 |
提示module没有ChatCompletion | openai SDK 版本过旧 | 执行pip show openai | 升级到 1.x 版本 |
| 请求超时 | base_url 配置错误或网络不可达 | 单独请求服务商接口测试连通性 | 修改 base_url,或调整超时时间 |
| JSON 解析失败 | 模型输出了多余文本 | 打印原始 response content | 增加 system 约束,解析失败时重试 |
| 上下文超限 | 历史消息过长 | 查看 Token 消耗 | 做滑动窗口截断或历史摘要 |
| RAG 检索结果不相关 | 文档切分不合理或向量检索不准 | 打印检索命中的文本块 | 优化切分策略,增加 top_k,尝试重排 |
| Gradio 界面打不开 | 端口被占用或未安装完整 | 查看终端日志 | 更换端口,或升级 gradio 版本 |
| 工具调用反复循环 | 缺少最大轮数限制 | 观察日志中的调用链 | 设定最大循环次数,校验工具输入 |
表格之外,再补一个最重要的原则:任何时候模型返回了你不期望的结果,第一件事都是打印原始响应内容,而不是猜。大模型应用的调试,靠的是看真实输入输出,而不是靠记忆。
11. 最佳实践与学习路线建议
如果你决定沿着 happy-llm 这条路系统学下去,下面几条建议可以帮你走得更稳。
第一,API Key 永远不要硬编码,也永远不要提交到 Git 仓库。.env文件要加入.gitignore。如果密钥已经泄露,第一时间去服务商后台吊销并重新生成。
第二,结构化输出一定要做异常兜底。模型不是数据库,不能保证每次都返回合法 JSON。在实际项目中,需要在解析失败时设计重试、修正或降级逻辑。
第三,RAG 项目先评估检索质量,再优化生成效果。很多 RAG 应用效果不好,问题不在模型,而在文档切分不合理、检索命中的内容不相关。上下文再强,喂进去的参考材料是错的,回答也不会对。
第四,Agent 工具调用要设置边界。真实项目里,工具背后都是真实操作。查询接口还好,如果是删除、写入、转账这类敏感操作,必须做权限校验、参数白名单和人工确认机制。
第五,学习路线上建议遵循“先原生,后框架”的顺序。先用原生 OpenAI SDK 跑通 API 调用、结构化输出、RAG、Function Calling,理解每一步在做什么,再去看 LangChain 这类框架,你会更容易看懂它的设计意图。反过来,一上来就用框架,很容易被抽象概念绕晕。
第六,验证每个节点时,不要只打印“运行成功”,要观察实际输出是否符合预期。学 LLM 应用开发和传统开发的另一个区别是:模型行为有随机性,同一个输入可能得到不同输出。所以测试时不要只看一次结果,要跑几次,观察稳定性。
12. 总结
这篇博客从 LLM 应用开发的真实痛点出发,围绕 datawhalechina/happy-llm 这个开源项目,梳理了一条从零到一的学习和技术实践路径:基础 API 调用、结构化输出、流式输出、多轮对话、RAG 检索增强生成、Agent 工具调用和 Gradio Web 应用。
最大的收获不是记住某个函数,而是理解 LLM 应用开发的主链路:模型提供语义理解和生成能力,工程代码负责上下文管理、输出约束、外部检索和工具执行。学完这一条链路,再去看任何框架或生产系统,都不会觉得它们不可理解。
建议你现在就照着第 4 章搭好环境,跑通第 5 章的第一个小程序,然后每天只推进一到两个节点。这种方式看起来慢,但每一步都有真实的代码反馈,比收藏几十篇教程然后在收藏夹里吃灰要有效得多。后面有机会,可以继续深入提示词技巧、RAG 的重排序与评估、Agent 多工具协作、模型微调,以及生产级部署。LLM 应用开发还在快速演进,但核心链路是稳定的,值得亲手跑一遍。