前言
在上一小节,我们简单介绍了langchain的使用方式,包括模型调用、工具调用、智能体功能以及上下文记忆。本小节我们将深入系统直接进行需求的分析的和智能体的搭载。
一、需求分析
对于AI技术的使用,明确需求是重中之重,以大语言模型为例,我们的提示词准确且完善,所得到的结果会更好。回归到我们AI视频监控系统,我希望大模型助手可以实现以下基本功能:
- 具备友好的对话和问答能力
- 具备一定记忆能力
- 依据用户提示进行RTSP摄像头的拉流与管理。
- 依据用户提示进行AI模型的加载和推理。
- 依据用户提示进行分析视频流和原始视频流的切换
二、需求拆解
具备友好的对话和问答能力
- 这一条本身就是伪命题,我们采用LLM进行对话的时候就已经具备了基本的功能,我们只需要采用langchain进行正确模型的调用,即可实现。
具备一定记忆能力
- 我们依据实现了带有记忆的大模型和agent应用,我们需要聚焦于“记住哪些,忽略哪些,利用哪些”,实现更智能的助手应用。
功能调用
- 对于学习了上一小节的朋友或者是对LLM了解的朋友应该很清楚,最直观的方式就是基于大模型的function calling功能,让大模型依据我们的需要进行正确工具的调用,最终实现相关的需求
三、实现与代码展示
- 这里直接给出实现的代码
from langchain_core.messages import ChatMessage, HumanMessage, ToolMessage
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import START, MessagesState, StateGraph
from langchain_ollama import ChatOllama
from langchain_core.tools import tool
import yaml
cur_info = {"open_camera": [],"yaml_path":r"path\to\config.yaml" }
import json
@tool
def check_info() -> dict:
"""
check global information
"""
global cur_info
return cur_info
@tool
def open_stream(cameraId: int, rtsp: str) -> bool:
"""
pulling stream
input:
cameraId : channel index(lobally Unique Identifier)
rtsp : pulling stream address
"""
global cur_info
import requests
data = {
"cameraId": cameraId,
"rtsp": rtsp,
}
print(f"Opening stream: cameraId={cameraId}, rtsp={rtsp}")
cur_info["open_camera"].append({"cameraId": cameraId, "rtsp": rtsp})
return True
@tool
def operation(type: str, **kwargs) -> str:
"""
type: operation type [open , close]
**kwargs: 参数通过关键字传递
open: 需要 cameraId 和 rtsp
close: 需要 cameraId
"""
# llm调用存在参数传递的格式问题
global cur_info
try:
kwargs = kwargs["kwargs"]
if isinstance(kwargs, list):
return "failed: just open one camera at the same time"
if type == "open":
# 检查必需参数
if 'cameraId' not in kwargs or 'rtsp' not in kwargs:
return "failed: open operation requires cameraId and rtsp"
camera_id = kwargs['cameraId']
rtsp = kwargs['rtsp']
for info in cur_info["open_camera"]:
if info["cameraId"] == str(camera_id):
return f"failed: camera {camera_id} has opened! please dont open again."
if info["rtsp"] == rtsp:
return f"failed: rtsp {rtsp} has opened with camera_id {info['cameraId']}!"
cur_info["open_camera"].append({"cameraId": str(camera_id), "rtsp": rtsp})
# 执行打开操作
return f"success: opened camera {camera_id} with rtsp {rtsp}"
elif type == "close":
if 'cameraId' not in kwargs:
return "failed: close operation requires cameraId"
camera_id = kwargs['cameraId']
# 执行关闭操作
index = -1
for cur_index, info in enumerate(cur_info["open_camera"]):
if info["cameraId"] == str(camera_id):
index = cur_index
break
if index != -1:
cur_info["open_camera"].pop(index)
return f"success: closed camera {camera_id}"
else:
return f"failed: camera {camera_id} not exists"
else:
return "failed: operation type must be 'open' or 'close'"
except Exception as e:
return e
@tool
def load_yaml_config(config_path='config.yaml'):
"""
加载 YAML 配置文件
Args:
config_path: YAML 配置文件路径
Returns:
解析后的配置字典,如果出错返回 None
"""
try:
with open(config_path, 'r', encoding='utf-8') as file:
config = yaml.safe_load(file)
return config
except FileNotFoundError:
return f"配置文件 {config_path} 不存在"
except yaml.YAMLError as e:
return f"配置文件 {config_path} 格式错误: {e}"
except Exception as e:
return f"读取文件时发生未知错误: {e}"
tools = [operation, check_info,load_yaml_config]
# Define a new graph
workflow = StateGraph(state_schema=MessagesState)
llm = ChatOllama(
base_url="path/to/ollama", # 添加了协议和端口
model="model_name",
validate_model_on_init=True,
temperature=0.0,
num_predict=1024,
).bind_tools(tools)
# Define the function that calls the model
def call_model(state: MessagesState):
response = llm.invoke(state["messages"])
return {"messages": response}
# Define the (single) node in the graph
workflow.add_edge(START, "model")
workflow.add_node("model", call_model)
# Add memory
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)
# 使用固定的thread_id来保持对话上下文
config = {"configurable": {"thread_id": "terminal_chat_session"}}
print("欢迎使用聊天机器人! 输入 'quit' 或 'exit' 退出程序。")
while True:
# try:
# 获取用户输入
user_input = input("\n用户: ").strip()
# 检查退出条件
if user_input.lower() in ['quit', 'exit', 'bye']:
print("再见!")
break
if not user_input:
continue
# 创建输入消息
input_messages = [HumanMessage(user_input)]
# 调用模型
output = app.invoke({"messages": input_messages}, config)
# 检查是否有工具调用
while len(output["messages"]) > 0 and hasattr(output["messages"][-1], 'tool_calls') and len(
output["messages"][-1].tool_calls) > 0:
# 处理工具调用
for tool_call in output["messages"][-1].tool_calls:
# try:
# 选择并执行工具
selected_tool = \
{"operation": operation, "check_info": check_info,"load_yaml_config":load_yaml_config}[
tool_call["name"].lower()]
print(f"调用工具: {tool_call['name']} 参数: {tool_call['args']}")
tool_output = selected_tool.invoke(tool_call["args"])
print(f"工具返回: {tool_output}")
# 添加工具响应消息
input_messages.append(ToolMessage(str(tool_output), tool_call_id=tool_call["id"]))
# except Exception as e:
# print(f"工具执行错误: {e}")
# input_messages.append(ToolMessage(f"Error: {str(e)}", tool_call_id=tool_call["id"]))
# 再次调用模型处理工具结果
output = app.invoke({"messages": input_messages}, config)
# 显示最终回复
if len(output["messages"]) > 0:
last_message = output["messages"][-1]
if hasattr(last_message, 'content'):
print(f"\n助手: {last_message.content}")
else:
print("\n助手: (无文本回复)")
else:
print("\n助手: (无回复)")
#-------------------------------------------------------------------------------------------------------
# 结果展示
欢迎使用聊天机器人! 输入 'quit' 或 'exit' 退出程序。
**用户**: 你好,我是AI视频监控系统,请问你是谁?
**助手**: 您好!我是阿里云开发的助手Qwen。我在这里可以帮助您解答问题、编写代码或者完成各种任务。请问您需要什么帮助呢?如果您是AI视频监控系统的管理员或操作员,我可以协助您进行摄像头的操作和配置检查等任务。请告诉我具体的需求吧。
**用户**: 你好,请问我是谁?
**助手**: 您好!根据之前的对话内容,您似乎是AI视频监控系统的一部分或者是该系统的使用者。请问您需要了解更具体的哪方面的信息呢?如果有关于摄像头操作或配置的问题,我可以帮助解答和执行相关任务。请详细说明您的需求。
**用户**: 好的,请你根据系统配置信息,打开对应的摄像头
调用工具: check_info 参数: {}
工具返回: {'open_camera': [], 'yaml_path': 'langchain_test\\config.yaml'}
调用工具: load_yaml_config 参数: {'config_path': '/path/to/config.yaml'}
工具返回: 配置文件 /path/to/config.yaml 不存在
**助手**: 看起来提供的配置路径 `/path/to/config.yaml` 并不存在。请提供正确的 YAML 配置文件路径,以便我可以从中获取摄像头的 ID 和 RTSP 地址等信息来打开对应的摄像头。
您可以再次尝试并提供一个有效的路径吗?如果不确定具体路径,请告知我您希望操作的具体摄像头名称或其他标识符,我会尽力帮助您找到正确的配置。
**用户**: 好的,我的配置路径在./config.yaml,请你读取内容并显示
调用工具: load_yaml_config 参数: {'config_path': './config.yaml'}
工具返回: {'rtsp': ['rtsp://camera1', 'rtsp://camera2', 'rtsp://camera3', 'rtsp://camera4', 'rtsp://camera5', 'rtsp://camera6', 'rtsp://camera7', 'rtsp://camera8', 'rtsp://camera9', 'rtsp://camera10']}
**助手**: 我已经读取了配置文件 `./config.yaml` 并获取到了摄像头的 RTSP 地址列表。现在请告诉我您希望打开哪个具体的摄像头,以便我可以继续进行操作。
例如,如果您想打开第一个摄像头(RTSP地址为 `rtsp://192.168.10.207:554/camera1`),请告知我对应的摄像头 ID 或名称。如果配置文件中有定义,请提供相应的标识符。如果没有明确的标识符,您可以直接告诉我您想打开哪个位置或编号的摄像头。
请问您希望操作哪一个摄像头呢?
**用户**: 当前一共有多少摄像头信息?
助手: 根据读取到的配置文件内容,总共有 10 个摄像头的信息,它们的 RTSP 地址如下:
- `rtsp://camera1`
- `rtsp://camera2`
- `rtsp://camera3`
- `rtsp://camera4`
- `rtsp://camera5`
- `rtsp://camera6`
- `rtsp://camera7`
- `rtsp://camera8`
- `rtsp://camera9`
- `rtsp://camera10`
请问您想操作其中的哪一个摄像头呢?请提供具体的摄像头 ID 或名称。
**用户**: 请你帮我打开这10个摄像头的视频流
调用工具: operation 参数: {'kwargs': {'cameraId': 1, 'rtsp': 'rtsp://camera1'}, 'type': 'open'}
工具返回: success: opened camera 1 with rtsp rtsp://camera1
调用工具: operation 参数: {'kwargs': {'cameraId': 2, 'rtsp': 'rtsp://camera2'}, 'type': 'open'}
工具返回: success: opened camera 2 with rtsp rtsp://camera2
调用工具: operation 参数: {'kwargs': {'cameraId': 3, 'rtsp': 'rtsp://camera3'}, 'type': 'open'}
工具返回: success: opened camera 3 with rtsp rtsp://camera3
调用工具: operation 参数: {'kwargs': {'cameraId': 4, 'rtsp': 'rtsp://camera4'}, 'type': 'open'}
工具返回: success: opened camera 4 with rtsp rtsp://camera4
调用工具: operation 参数: {'kwargs': {'cameraId': 5, 'rtsp': 'rtsp://camera5'}, 'type': 'open'}
工具返回: success: opened camera 5 with rtsp rtsp://camera5
调用工具: operation 参数: {'kwargs': {'cameraId': 6, 'rtsp': 'rtsp://camera6'}, 'type': 'open'}
工具返回: success: opened camera 6 with rtsp rtsp://camera6
调用工具: operation 参数: {'kwargs': {'cameraId': 7, 'rtsp': 'rtsp://camera7'}, 'type': 'open'}
工具返回: success: opened camera 7 with rtsp rtsp://camera7
调用工具: operation 参数: {'kwargs': {'cameraId': 8, 'rtsp': 'rtsp://camera8'}, 'type': 'open'}
工具返回: success: opened camera 8 with rtsp rtsp://camera8
调用工具: operation 参数: {'kwargs': {'cameraId': 9, 'rtsp': 'rtsp://camera9'}, 'type': 'open'}
工具返回: success: opened camera 9 with rtsp rtsp://camera9
调用工具: operation 参数: {'kwargs': {'cameraId': 10, 'rtsp': 'rtsp://camera10'}, 'type': 'open'}
工具返回: success: opened camera 10 with rtsp rtsp://camera10
**助手**: 所有 10 个摄像头的视频流都已经成功打开。每个摄像头都通过其对应的 RTSP 流地址连接到了指定的服务器。
如果您需要进一步的帮助或有其他请求,请告诉我!
- 执行流程图如下所示:
- 流程甘特图如下所示:
总结
以上,我们已经实现了一个基本的AI大模型助手,具备上文理解、记忆以及工具的调用能力,至此,我们实现了完整的技术栈和技术路线,希望大家伙可以通过本书实现对新技术的理解,同时可以很好的契合已有技术,实现行业和技术的发展。

287

被折叠的 条评论
为什么被折叠?



