重点
- 第 0 课真正要看懂的是这条链路:
- 用户敲
hx ask "你是谁"。 - Node.js 收到命令参数。
- 代码把参数变成
messages。 fetch()发一个 HTTP POST 请求给 DeepSeek。- DeepSeek 返回 JSON。
- 代码从 JSON 里取出 assistant 的文本。
- 用户敲
- Agent / Harness 往后会变复杂,但
第一性原理很简单:- 本质就是一个程序。
- 程序把上下文组织成 HTTP 请求。
- 大模型根据请求返回下一句话。
- 程序再决定把这句话显示出来,还是继续做别的事。
一眼看懂
- 当前课程的关键判断:
hx是触发程序的入口。messages是发给模型的上下文。fetch()是程序和大模型之间的一来一回。
流程图
① 从命令到请求
② HTTP 一来一回
③ 数据形状怎么变
④ 多轮对话的一来一回
入口
先看命令怎么连到文件。
json
{
"bin": {
"hx": "./src/index.js"
}
}- 这段配置只解决一件事:
- 用户敲
hx。 - npm 找到
src/index.js。 - Node.js 执行这个文件。
- 用户敲
文件第一行写:
js
`#!/usr/bin/env` node
// 这行告诉系统:
// 当前文件要交给 node 执行。真正的参数入口是:
js
runCli(process.argv.slice(2));
// 用户输入:
// hx ask "你是谁"
//
// process.argv 大概是:
// [
// "/path/to/node",
// "/path/to/hx",
// "ask",
// "你是谁"
// ]
//
// slice(2) 之后,业务代码只看到:
// ["ask", "你是谁"]命令分发
runCli() 做第一层分发。
js
async function runCli(argv) {
const [command, ...rest] = argv;
if (command === "hello") {
console.log("HarnessX CLI is running.");
return;
}
if (command === "ask") {
// rest 是 ["你是谁"]
// join 之后得到真正要发给模型的 prompt。
const prompt = rest.join(" ").trim();
await askOnce(prompt);
return;
}
if (command === "chat") {
// chat 会在一个进程里持续维护 messages。
await chat(rest.join(" ").trim());
return;
}
}- 这里要记住:
hello只验证入口。ask做一次模型请求。chat做多轮模型请求。
发出去的是什么
askOnce() 先把用户输入变成 messages。
js
async function askOnce(prompt) {
const messages = [
systemMessage(),
{ role: "user", content: prompt },
];
const answer = await callDeepSeek(messages);
console.log(answer);
}假设用户输入:
bash
hx ask "你是谁"那发给 DeepSeek 的核心请求体就是:
json
{
"model": "deepseek-chat",
"messages": [
{
"role": "system",
"content": "你是 HarnessX 第 0 课的对话助手。\n用中文回答,保持简短具体。\n当前只验证 Node.js CLI 入口和 DeepSeek 多轮对话。"
},
{
"role": "user",
"content": "你是谁"
}
],
"stream": false
}- 这就是最关键的东西:
model:- 告诉服务端调用哪个模型。
messages:- 告诉模型当前对话上下文。
stream: false:- 这次先等完整回答返回。
代码里对应这一段。
js
const requestBody = {
model,
messages,
stream: false,
};怎么发出去
真正发出去的是 fetch()。
js
const response = await fetch("https://api.deepseek.com/chat/completions", {
method: "POST",
headers: {
// 告诉服务端:我发的是 JSON。
"Content-Type": "application/json",
// 告诉服务端:我是谁,我有没有权限调用。
Authorization: `Bearer ${apiKey}`,
},
// HTTP 请求体必须是字符串,所以要 JSON.stringify。
body: JSON.stringify(requestBody),
});- 这段代码说明 Agent 的底层动作:
- 程序组织上下文。
- 程序带上 API Key。
- 程序向一个 URL 发 HTTP POST。
如果要用更朴素的话讲:
text
我把聊天记录整理成 JSON,
带上 API Key,
POST 到 DeepSeek 的接口,
等它返回一个 JSON。返回的是什么
DeepSeek 返回的是一个 JSON。
大概长这样:
json
{
"choices": [
{
"message": {
"role": "assistant",
"content": "我是 HarnessX 第 0 课的对话助手。"
}
}
]
}当前代码只取这一段:
js
const payload = await response.json();
const content = payload?.choices?.[0]?.message?.content;
return content;- 这里要记住:
response是 HTTP 响应。payload是响应 JSON。choices[0].message是模型给出的第一条回复。content是最终要打印给用户看的文本。
多轮对话
多轮对话的本质是:每次请求都带上更长的 messages。
第一轮:
json
[
{ "role": "system", "content": "你是 HarnessX 第 0 课的对话助手。" },
{ "role": "user", "content": "我的名字是 liguwe" }
]模型回答后,代码把 assistant 也追加进去。
json
[
{ "role": "system", "content": "你是 HarnessX 第 0 课的对话助手。" },
{ "role": "user", "content": "我的名字是 liguwe" },
{ "role": "assistant", "content": "你好,liguwe。" }
]第二轮用户再问:
json
[
{ "role": "system", "content": "你是 HarnessX 第 0 课的对话助手。" },
{ "role": "user", "content": "我的名字是 liguwe" },
{ "role": "assistant", "content": "你好,liguwe。" },
{ "role": "user", "content": "我刚才说我的名字是什么?" }
]模型能答出来的原因,是代码每次都把历史消息重新发给它。
这段代码就是关键:
js
// 用户说一句,追加到 messages。
messages.push({ role: "user", content: text });
// 把完整 messages 发给模型。
const answer = await callDeepSeek(messages);
// 模型回一句,也追加到 messages。
messages.push({ role: "assistant", content: answer });怎么跑
先验证入口。
bash
npm link
hx --help
hx hello看到这个输出,说明 CLI 入口通了。
text
HarnessX CLI is running.再配置模型。
bash
cp .env.example .env.env 里填:
env
DEEPSEEK_API_KEY=你的 key
DEEPSEEK_MODEL=deepseek-chat跑一次 HTTP 调用。
bash
hx ask "只回复 OK"预期看到:
text
OK跑多轮对话。
bash
hx chat输入:
text
hx> 我的名字是 liguwe
hx> 我刚才说我的名字是什么?- 如果第二句能回答出来:
- 说明
messages被持续追加。 - 说明每次 HTTP 请求都带上了上下文。
- 说明
这一课真正要记住
hx:- 只是触发 Node.js 程序的入口。
process.argv:- 把用户命令变成 JS 数组。
messages:- 把用户输入和历史上下文组织成模型能读的格式。
fetch():- 把
messages作为 HTTP 请求体发给 DeepSeek。
- 把
payload.choices[0].message.content:- 从 HTTP 响应里取出模型回答。
第 0 课最终要讲清楚的是这句话:
text
Agent 的起点,就是程序把上下文打包成 HTTP 请求发给大模型,
再从 HTTP 响应里取出模型的下一句话。