先放源码,非常简单,使用nodejs和原生javascript fetch调用大模型的HTTP接口(使用OpenAI传统chat接口),完成监控摄像头的运营管理的小例子。

注:测试使用的是随便找的一家平台服务商,用的deepseek和minimax模型测试,其它兼容OpenAI接口规范的一样。并且这段代码也是另一个编程智能体写的——Visual Studio Code的Copilot免费额度生成。仅作概念讲解,勿无脑照搬。

其主要逻辑就是:

  • 定义给AI调用的工具(用人话给AI描述工具是干什么的,再映射到真实程序是怎样实现的,类似现在大火的Skills但那个要更复杂些)
  • 从命令行输入读取用户输入命令(并记住所有的输入输出的文字,类似会话上下文的概念,实际工程要更复杂)
  • 封装请求通过HTTP接口发给大模型平台(OpenAI定义的事实上的标准API接口格式,虽然它家出了新版本接口)
  • 平台返回大模型生成的文字(或匹配到工具调用参数)
  • 执行工具调用,并将工具的输出归并再发给大模型
  • 多次循环调用工具直到不再需要工具调用
  • 大模型综合分析所有输入和输出得出结论

虽然简单,但已经描述清楚一个典型的智能体底下究竟干了什么——利用大模型来理解自然语言的需求输入,再综合分析人和机器的各种输入、输出来得出结果。

大模型不干具体的活,它只是负责分析需求输入,找工具人干活,再拼装零碎的中间输出内容,形成最后输出。妥妥的产品经理是不是,哈哈哈哈……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { execSync } from 'child_process';
import readline from 'readline';

// const MODEL = "minimax/minimax-m2.1";
const MODEL = 'deepseek/deepseek-v3.2-251201';

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

const tools = [
{
"type": "function",
"function": {
name: "get_cameras",
description: "获取所有在线的网络摄像头",
}
},{
"type": "function",
"function": {
name: "check_camera",
description: "检查输入的指定摄像头的状态",
parameters: {
type: "object",
properties: {
url: { type: "string", description: "指定摄像头的RTSP地址" }
},
required: ["url"]
}
}
}
];

let messages = [
{ role: 'system', content: '你是 AI Agent,分析用户意图并决定要调用哪个工具。' }
];

async function runTool(action, params) {
if (action === "get_cameras") {
// 模拟 API
const cameras = [
{id:1, name:'门口', url:'rtsp://172.21.132.230/url1'},
{id:2, name:'办公室', url:'rtsp://172.21.132.230:554/rtp/32020000002000000003_32020000001320000020?originTypeStr=rtp_push'},
{id:3, name:'广场', url:'rtsp://172.21.132.230/url3'},
];
let resp = '';
cameras.forEach(cam => {
resp += `摄像头名称:${cam.name},编号id:${cam.id},RTSP播放地址:${cam.url}\r\n`;
})
return resp;
}else if (action === "check_camera") {
const url = params.url;
console.log(`执行命令:ffplay -loglevel debug '${url}',3秒钟,并检查输出内容判断是否正常。`);
try {
const output = execSync(
`ffplay -t 3 -loglevel debug -i '${url}'`,
{
stdio: ['ignore', 'pipe', 'pipe'], // 捕获 stdout + stderr
encoding: 'utf8',
timeout: 3000 // 3 秒超时
}
);
console.log('ffplay output:\n', output);
return "ffplay执行输出:" + output;
} catch (err) {
// ffplay 被 Ctrl+C / SIGINT / 超时杀掉时也会走这里
console.error('ffplay exited');
return "ffplay命令执行失败:"+err.stdout?.toString()+err.stderr?.toString();
}

}
return "未知工具";
}

async function sendMessage() {
while (true) {
const userInput = await new Promise(resolve => rl.question('用户输入: ', resolve));
if (userInput.toLowerCase() === 'exit') break;

messages.push({ role: 'user', content: userInput });

let done = false;
while (!done) {
console.log("ai 请求发送。。\r\n")

const response = await fetch('https://api.qnaigc.com/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.OPENAI_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
stream: false,
model: MODEL,
messages: messages,
tools: tools,
tool_choice: 'auto'
})
});

const data = await response.json();
if(data.error){
console.error('Error:', data.error);
break;
}

console.log("token usage:%d", data.usage?.total_tokens);

const message = data.choices[0].message;

let aiReply;
if (message.tool_calls) {
let toolCall = message.tool_calls[0];
console.log(toolCall, message.content);
// 模型选择了工具
const action = toolCall.function.name;
const params = JSON.parse(toolCall.function.arguments);
// 这里可以调用对应工具
aiReply = await runTool(action, params);
messages.push({"role": "tool", "tool_call_id": toolCall.id, "content": aiReply})
}else if (message.content) {
// 普通文本
aiReply = message.content;
console.log('AI:', aiReply);
}
messages.push({"role": "assistant", "content": aiReply})

// 检查工具调用
const toolCalls = message.tool_calls || [];
if (toolCalls.length === 0) {
// console.log("\r\n -----DEBUG------");
// messages.forEach((item, i) => {
// console.log(`message ${i}:`, item);
// });
// console.log("\r\n -----DEBUG end------");

console.log("agent结束,等待下一个指令")
done = true;
break;
}else{
console.log("\r\n------\r\nagent下一步。。。\r\n")
messages.push({ role: 'user', content: "下一步。" });
}
}
}

rl.close();
}

sendMessage();

实际测试日志,可以清晰看到大模型的思维和决策路线:

它能理解你要干什么,能自动解析任务格式的摄像头数据结构并找到你说的“前台”“办公室”是指的摄像头名称,能理解你工具里定义的检测摄像头状态就是用ffplay去尝试播放一下再看日志输出,能解析出日志里播放状态和播放的视频参数格式。

如果使用传统编码方法,你可能需要定义复杂的正则匹配或者更专业一些的编程技巧(例如:tcp连接探测,rtsp协议分析等等),而AI智能体,可以通过有限的信息综合上下文进行判断,容错性也更高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
node test.js 
用户输入: 帮我看下办公室摄像头工作正常吗
ai 请求发送。。

token usage:565
{
index: 0,
type: 'function',
id: 'call_dxyjcv62vdedg05gvh2mcg30',
function: { name: 'get_cameras', arguments: '{}' }
} 我需要帮您检查办公室摄像头的工作状态。不过,要检查特定的摄像头,我需要知道它的RTSP地址。

您能提供一下办公室摄像头的RTSP地址吗?或者您可以先使用"获取所有在线的网络摄像头"功能来查看当前有哪些摄像头在线,这样我可以帮您找到办公室摄像头对应的地址。

您希望我:
1. 先获取所有在线摄像头列表,然后您告诉我哪个是办公室摄像头?
2. 还是您直接提供办公室摄像头的RTSP地址?



------
agent下一步。。。

ai 请求发送。。

token usage:899
{
index: 0,
type: 'function',
id: 'call_7a7e429f43c44eb7866c1bce',
function: {
name: 'check_camera',
arguments: '{"url": "rtsp://172.21.132.230:554/rtp/32020000002000000003_32020000001320000020?originTypeStr=rtp_push"}'
}
} 我来帮您检查办公室摄像头的状态。根据您提供的摄像头列表,办公室摄像头的RTSP地址是:rtsp://172.21.132.230:554/rtp/32020000002000000003_32020000001320000020?originTypeStr=rtp_push

让我检查一下这个摄像头的状态:


执行命令:ffplay -loglevel debug 'rtsp://172.21.132.230:554/rtp/32020000002000000003_32020000001320000020?originTypeStr=rtp_push',3秒钟,并检查输出内容判断是否正常。
ffplay exited

------
agent下一步。。。

ai 请求发送。。

token usage:18401
AI: 根据之前的摄像头列表和检查结果,办公室摄像头的状态如下:

**办公室摄像头状态:**
- **摄像头名称**:办公室
- **编号id**:2
- **RTSP播放地址**:rtsp://172.21.132.230:554/rtp/32020000002000000003_32020000001320000020?originTypeStr=rtp_push
- **状态**:**工作正常**

**检测详情:**
1. 摄像头能够成功连接
2. 视频流格式:H.264 (Main)
3. 分辨率:1280×720
4. 帧率:约25 fps
5. 视频编码正常,能够解码播放

**其他摄像头状态:**
1. **门口摄像头**(id:1):RTSP地址:rtsp://172.21.132.230/url1
2. **广场摄像头**(id:3):RTSP地址:rtsp://172.21.132.230/url3

**结论:** 办公室摄像头工作正常,视频流稳定可用。如果您需要检查其他摄像头,请告诉我。
agent结束,等待下一个指令
用户输入: 门口的呢
ai 请求发送。。

token usage:18280
{
index: 0,
type: 'function',
id: 'call_28q1ooyzibxvsxx22zbl8062',
function: {
name: 'check_camera',
arguments: '{"url": "rtsp://172.21.132.230/url1"}'
}
} 我来检查门口摄像头的状态。


执行命令:ffplay -loglevel debug 'rtsp://172.21.132.230/url1',3秒钟,并检查输出内容判断是否正常。
ffplay exited

------
agent下一步。。。

ai 请求发送。。

token usage:22676
AI: 根据检查结果,门口摄像头的状态如下:

**门口摄像头状态:**
- **摄像头名称**:门口
- **编号id**:1
- **RTSP播放地址**:rtsp://172.21.132.230/url1
- **状态**:**连接失败**

**检测详情:**
1. 摄像头能够建立TCP连接(成功连接到172.21.132.230:554端口)
2. 但是无法获取有效的视频流数据
3. 连接保持活跃但无视频流响应

**可能的原因:**
1. RTSP地址可能不正确(url1可能不是有效的流地址)
2. 摄像头可能未正确配置或已关闭
3. 视频流服务可能未启动
4. 权限或认证问题

**建议检查:**
1. 确认RTSP地址是否正确
2. 检查摄像头硬件是否正常开启
3. 验证摄像头配置
4. 尝试使用其他RTSP客户端连接测试

**当前摄像头状态总结:**
- ✅ **办公室摄像头**:工作正常(1280×720,25fps)
- ❌ **门口摄像头**:连接失败,需要进一步检查
- **广场摄像头**:尚未检查

您需要我检查广场摄像头吗?或者需要我提供更详细的故障排查建议?
agent结束,等待下一个指令
用户输入: 广场
ai 请求发送。。

token usage:22926
{
index: 0,
type: 'function',
id: 'call_24132e652d50457494315ffc',
function: {
name: 'check_camera',
arguments: '{"url": "rtsp://172.21.132.230/url3"}'
}
}


执行命令:ffplay -loglevel debug 'rtsp://172.21.132.230/url3',3秒钟,并检查输出内容判断是否正常。
ffplay exited

------
agent下一步。。。

ai 请求发送。。

token usage:27281
AI: 根据检查结果,广场摄像头的状态如下:

**广场摄像头状态:**
- **摄像头名称**:广场
- **编号id**:3
- **RTSP播放地址**:rtsp://172.21.132.230/url3
- **状态**:**连接失败**

**检测详情:**
1. 摄像头能够建立TCP连接(成功连接到172.21.132.230:554端口)
2. 但是无法获取有效的视频流数据
3. 连接保持活跃但无视频流响应
4. 与门口摄像头的情况类似

**可能的原因:**
1. RTSP地址可能不正确(url3可能不是有效的流地址)
2. 摄像头可能未正确配置或已关闭
3. 视频流服务可能未启动
4. 权限或认证问题

**所有摄像头状态总结:**

| 摄像头 | 状态 | 详细情况 |
|--------|------|----------|
| **办公室摄像头** | ✅ **工作正常** | H.264,1280×720,25fps,视频流畅 |
| **门口摄像头** | ❌ **连接失败** | 能建立TCP连接,但无视频流响应 |
| **广场摄像头** | ❌ **连接失败** | 能建立TCP连接,但无视频流响应 |

**问题分析:**
1. 办公室摄像头工作正常,说明RTSP服务器本身是正常运行的
2. 门口和广场摄像头连接失败,可能是:
- RTSP地址配置错误(url1和url3可能不是正确的流路径)
- 这两个摄像头可能未开启或未正确配置
- 可能需要特定的流名称或认证信息

**建议:**
1. 检查门口和广场摄像头的实际RTSP地址是否正确
2. 确认这两个摄像头的硬件状态(是否开启)
3. 检查摄像头的配置参数
4. 可能需要联系系统管理员查看具体的流地址配置

您需要我提供进一步的故障排查步骤吗?
agent结束,等待下一个指令
用户输入: