实现自己的AI视频监控系统-第四章-基于langchain的AI大模型与智能体应用2

AI 智能体本地部署实战

OpenClaw 从环境搭建到避坑全攻略,本地跑通你的 AI 代理


前言

在上一小节,我们简单介绍了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 流地址连接到了指定的服务器。

如果您需要进一步的帮助或有其他请求,请告诉我!
  • 执行流程图如下所示:
工具函数
operation
check_info
open_stream
load_yaml_config
开始
初始化全局变量和工具函数
构建LangGraph工作流
配置记忆存储和会话ID
等待用户输入
用户输入文本指令
检查退出条件?
退出程序
创建HumanMessage
调用工作流app.invoke
模型推理
是否有工具调用?
遍历每个工具调用
选择并执行对应工具
添加ToolMessage到消息历史
再次调用模型
生成最终文本响应
打印助手回复
  • 流程甘特图如下所示:
00:00:00 00:00:05 00:00:10 00:00:15 00:00:20 00:00:25 00:00:30 00:00:35 00:00:40 00:00:45 00:00:50 00:00:55 加载全局变量和工具 构建工作流图 配置记忆和会话 等待用户输入 处理用户输入 模型推理 检查工具调用 执行工具函数 添加工具结果 再次模型推理 生成最终响应 输出回复 下一次等待输入 初始化阶段 用户交互循环 工具调用循环 响应生成 下一次循环 AI视频监控助手执行流程

总结

以上,我们已经实现了一个基本的AI大模型助手,具备上文理解、记忆以及工具的调用能力,至此,我们实现了完整的技术栈和技术路线,希望大家伙可以通过本书实现对新技术的理解,同时可以很好的契合已有技术,实现行业和技术的发展。


AI 智能体本地部署实战

OpenClaw 从环境搭建到避坑全攻略,本地跑通你的 AI 代理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值