19.LangChain框架7-LangChain1.0版本使用Agent(中间件实例)

内容参考于:图灵AI大模型全栈

后面会有专门的Agent详细玩法,这里只是做一个了解

1.0的调用方式比0.3版本简单很多

运行下方的代码,要用1.0.2版本,否则会报错

shell 复制代码
pip install langgraph==1.0.2 langgraph-prebuilt==1.0.2

效果图:

python 复制代码
# 导入LangChain智能体创建函数:用于构建AI智能客服代理
# 来源:langchain 核心库
from langchain.agents import create_agent
# 导入Tavily在线搜索工具:用于联网搜索实时信息
# 来源:langchain_tavily 第三方工具库
from langchain_tavily import TavilySearch
# 导入大模型调用类:对接阿里通义千问大模型
# 来源:langchain_openai 官方库
from langchain_core.tools import Tool
# 导入LangChain工具类:用于封装自定义函数为AI可调用工具
# 来源:langchain_core 核心库
import os
# 导入Python内置模块:用于读取环境变量、文件操作
# 来源:Python官方内置库
from dotenv import load_dotenv
# 导入环境变量加载工具:读取.env文件中的API密钥
# 来源:第三方库 python-dotenv

# 作用:加载项目根目录下的.env环境变量文件
# 入参:无
# 可传值:无
# 值来源:固定调用
load_dotenv()


# 定义查询订单状态的函数
# 作用:根据订单ID查询订单状态(自定义业务函数)
# 入参:order_id -> 字符串/数字,订单编号
# 可传值:任意订单号(仅1024有预设结果)
# 值来源:用户提问中提取的订单ID
# 返回值:订单状态描述字符串
def query_order_status(order_id):
    if order_id == "1024":
        return "订单 1024 的状态是:已发货,预计送达时间是 3-5 个工作日。"
    else:
        return f"未找到订单 {order_id} 的信息,请检查订单号是否正确。"


# 定义退款政策说明函数
# 作用:根据公司名称查询对应退款政策(自定义业务函数)
# 入参:company_name -> 字符串,公司名称
# 可传值:任意公司名称(仅tom有预设结果)
# 值来源:用户提问中提取的公司名称
# 返回值:退款政策描述字符串
def company_refund_policy(company_name):
    print(company_name)
    if company_name == "tom":
        return "tom公司的退款政策是:在购买后7天内可以申请全额退款,需提供购买凭证。"
    else:
        print('输入有误')


# 查询年龄
# 作用:根据姓名查询年龄(自定义业务函数)
# 入参:name -> 字符串,姓名
# 可传值:任意姓名(仅tom有预设结果)
# 值来源:用户提问中提取的姓名
# 返回值:年龄描述字符串
def get_age(name):
    if name == "tom":
        print(name)
        return "我的年龄是56岁!"
    else:
        print('输入有误')


# 初始化工具列表:将在线搜索+自定义函数封装为AI工具
# 作用:给智能体提供可调用的工具集合
# 入参:列表格式,包含Tool对象和TavilySearch对象
# 值来源:自定义编写的工具+官方搜索工具
tools = [
    # Tavily联网搜索工具
    # 作用:实时联网搜索互联网信息
    # max_results:搜索返回结果数量,int
    # 可传值:1-5
    # 值来源:自定义设置
    # tavily_api_key:Tavily平台的API密钥
    # 可传值:Tavily官网申请的密钥
    # 值来源:.env文件中的TAVILY_API_KEY
    TavilySearch(max_results=1, tavily_api_key=os.getenv("TAVILY_API_KEY")),

    # 自定义工具1:查询订单状态
    Tool(
        name="queryOrderStatus",  # 工具名称:唯一标识,智能体通过名称调用
        func=query_order_status,  # 工具绑定的函数:调用后执行的自定义函数
        description="根据订单ID查询订单状态",  # 工具描述:告诉智能体该工具的用途
        args={"order_id": "订单的ID"}  # 工具参数:参数名+参数说明
    ),

    # 自定义工具2:查询公司退款政策
    Tool(
        name="companyRefundPolicy",  # 工具唯一名称
        func=company_refund_policy,  # 绑定退款政策函数
        description="查询某某公司退款政策详细内容",  # 工具用途描述
        args={"company_name": "公司名称"}  # 工具入参说明
    ),

    # 自定义工具3:查询年龄
    Tool(
        name="getAge",  # 工具唯一名称
        func=get_age,  # 绑定年龄查询函数
        description="查询tom年龄大小",  # 工具用途描述
        args={"name": "查询tom年龄大小"}  # 工具入参说明
    ),
]

# 选择将驱动代理的LLM
# 作用:初始化阿里通义千问大模型,作为智能体的大脑
# api_key:阿里通义API密钥
# 可传值:阿里云百炼平台申请的API Key
# 值来源:.env文件中的DASHSCOPE_API_KEY
# base_url:阿里通义兼容OpenAI格式的接口地址
# 可传值:官方固定地址
# 值来源:阿里云官方文档
# model:模型名称
# 可传值:qwen-plus、qwen-turbo、qwen-long等
# 值来源:阿里云官方模型列表
llm = ChatOpenAI(api_key=os.getenv("DASHSCOPE_API_KEY"),
                 base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
                 model='qwen-plus')

# 作用:创建AI智能客服代理
# model:绑定大模型(大脑)
# 可传值:初始化好的llm对象
# 值来源:上方定义的llm变量
# tools:绑定可调用的工具集合
# 可传值:上方定义的tools列表
# 值来源:自定义工具列表
# system_prompt:系统提示词,约束智能体行为
# 核心规则:必须用工具回答,参数传递准确,不篡改字符串
# 值来源:自定义编写的约束规则
agent = create_agent(
    model=llm,
    tools=tools,
    system_prompt="你是一个客服助手,使用工具回答问题。传递给工具的内容必须是准确的json数据不结尾的括号多加一个,如果是字符串数据必须和输入的保持一致要完整,不是篡改**重要规则**, ",
)

# 定义一些测试询问
# 作用:测试智能体的提问列表
# 入参:列表格式,字符串元素
# 可传值:任意自定义问题
# 值来源:自定义测试用例
queries = [
    "请问订单1024的状态是什么?",
    "请问tom公司退款政策是什么?",
    "2024年谁胜出了美国总统的选举"
]

# 运行代理并输出结果
# 作用:循环遍历测试问题,调用智能体并打印回答
# input:单个用户问题
# inputs:标准化输入格式,固定messages结构
# result:智能体返回的结果
# 输出:打印客户问题+智能体最终回答
for input in queries:
    print('客户提问:' + input)
    inputs = {"messages": [{"role": "user", "content": input}]}
    result = agent.invoke(inputs)
    print(result['messages'][-1].content)

中间件的作用

我们现在要访问国外的网站,我们在国内是没办法直接访问国外的网站,我们就需要先找一个可以访问国外网络的服务器,这个服务器就是一个中间件,我们通过访问中间件,然后中间件根据我们的请求,去访问国外的数据,然后中间件把国外的数据再发送给我们

LangChain的中间件

class 描述
SummarizationMiddleware 接近令牌限制时自动总结对话历史记录
HumanInTheLoopMiddleware 暂停执行,等待人工批准工具调用
ModelCallLimitMiddleware 限制模型调用次数,以防止成本过高。
ToolCallLimitMiddleware 通过限制调用次数来控制工具执行。
ModelFallbackMiddleware 主模型故障时自动回退到备用模型
PIIMiddleware 检测和处理个人身份信息
TodoListMiddleware 为代理人配备任务规划和跟踪功能
LLMToolSelectorMiddleware 在调用主模型之前,使用 LLM 选择相关工具。
ToolRetryMiddleware 使用指数退避算法自动重试失败的工具调用
LLMToolEmulator 使用LLM模拟工具执行以进行测试
ContextEditingMiddleware 通过精简或清除工具使用情况来管理对话上下文
ShellToolMiddleware 向代理公开持久 shell 会话以执行命令
FilesystemFileSearchMiddleware 提供对文件系统文件的 Glob 和 Grep 搜索工具
AgentMiddleware 用于创建自定义中间件的基础中间件类

使用中间,这里使用HumanInTheLoopMiddleware拦截Tavily搜索引擎

HumanInTheLoopMiddleware需要传递一个工具的名字,Tavily搜索引擎工具名字查看方式,按着CTRL鼠标左键单击下图红框

然后如下图红框,就可以看到名字了

然后HumanInTheLoopMiddleware里有三个值,按着CTRL鼠标左键单击下图红框

如下图红框,可以看到"approve", "edit", "reject",分别是允许、编辑、拒绝,意思是我们人工可以做什么,上图中只使用了运行和拒绝

如下图:ai回答的问题是空的,这是因为这个问题是需要调用工具,我们并没有同意它使用工具,所以回答的是空的

上图使用的代码

python 复制代码
from langchain.agents import create_agent
from langchain_tavily import TavilySearch
from langchain_openai import ChatOpenAI
from langchain_core.tools import Tool
import os
from dotenv import load_dotenv
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command

load_dotenv()


# 定义查询订单状态的函数
def query_order_status(order_id):
    if order_id == "1024":
        return "订单 1024 的状态是:已发货,预计送达时间是 3-5 个工作日。"
    else:
        return f"未找到订单 {order_id} 的信息,请检查订单号是否正确。"


# 定义退款政策说明函数
def company_refund_policy(company_name):
    print(company_name)
    if company_name == "tom":
        return "tom公司的退款政策是:在购买后7天内可以申请全额退款,需提供购买凭证。"
    else:
        print('输入有误')


# 查询年龄
def get_age(name):
    if name == "tom":
        print(name)
        return "我的年龄是56岁!"
    else:
        print('输入有误')


# 初始化工具
tools = [
    TavilySearch(max_results=1, tavily_api_key=os.getenv("TAVILY_API_KEY")),
    Tool(
        name="queryOrderStatus",
        func=query_order_status,
        description="根据订单ID查询订单状态",
        args={"order_id": "订单的ID"}
    ),
    Tool(
        name="companyRefundPolicy",
        func=company_refund_policy,
        description="查询某某公司退款政策详细内容",
        args={"company_name": "公司名称"}
    ),
    Tool(
        name="getAge",
        func=get_age,
        description="查询tom年龄大小",
        args={"name": "查询tom年龄大小"}
    ),
]

# 选择将驱动代理的LLM
llm = ChatOpenAI(api_key=os.getenv("DASHSCOPE_API_KEY"),
                 base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
                 model='qwen-plus')


agent = create_agent(
    model=llm,
    tools=tools,
    system_prompt="你是一个客服助手,使用工具回答问题。传递给工具的内容必须是准确的json数据不结尾的括号多加一个,如果是字符串数据必须和输入的保持一致要完整,不是篡改**重要规则**, ",
    # 使用中间件
    middleware=[
            # 用于指定需要人工判断工具是否可使用的
            HumanInTheLoopMiddleware(
                interrupt_on={
                    'tavily_search':{'allowed_decisions':["approve", "reject"]},
                    "queryOrderStatus": {'allowed_decisions': ['approve', 'reject']},
                    "companyRefundPolicy": {'allowed_decisions': ['approve', 'reject']},
                    "getAge": {'allowed_decisions': ['approve', 'reject']},
                }
        )
    ],
    # 让 agent 代理具备"记住对话历史 + 支持中断后继续"的能力
    checkpointer=InMemorySaver(),
)

# 定义一些测试询问
queries = [
    "请问订单1024的状态是什么?",
    "请问tom公司退款政策是什么?",
    "2024年谁胜出了美国总统的选举"
]

# 为当前会话设置唯一的 thread_id
# 同一个 thread_id 会保留完整的对话状态和中断历史
# 可以理解为一个ai工具运行一个会话,就跟我们使用ai时创建的新窗口一样
config = {"configurable": {"thread_id": "some_other_id_123"}}

# 运行代理并输出结果
for que in queries:
    print('客户提问:' + que)
    inputs = {"messages": [{"role": "user", "content": que}]}
    # # 执行代理,直到遇到中断或完成
    result = agent.invoke(inputs, config=config)
    print(result)

如下图红框__interrupt__这里面的是需要审批的工具,它里面会有用到的工具信息,还有用工具的原因

上图里json的说明

json 复制代码
# 智能体执行返回的完整结果字典 | 逐值注释,格式无任何改动
{
    # 键:messages 作用:存储对话所有消息 类型:列表 值来源:agent执行生成
    'messages': [
        # 用户消息对象 类型:HumanMessage 值来源:用户输入
        HumanMessage(
            content='请问订单1024的状态是什么?',  # 内容:用户提问 类型:str 值来源:自定义输入
            additional_kwargs={},                  # 附加参数 类型:dict 值来源:默认空
            response_metadata={},                  # 响应元数据 类型:dict 值来源:默认空
            id='1afd3b77-e1cd-4417-827a-746bab834aed'  # 唯一ID 类型:str 值来源:系统自动生成
        ),
        # AI消息对象 类型:AIMessage 值来源:大模型返回
        AIMessage(
            content='',  # AI回答内容 类型:str 空值=调用工具 值来源:大模型返回
            additional_kwargs={'refusal': None},  # 附加配置 类型:dict refusal=无拒绝 值来源:大模型返回
            response_metadata={  # 响应元数据 类型:dict 值来源:大模型接口返回
                'token_usage': {  # token消耗 类型:dict 值来源:计费统计
                    'completion_tokens': 25,            # 输出token数 类型:int 值来源:模型计算
                    'prompt_tokens': 1948,              # 输入token数 类型:int 值来源:模型计算
                    'total_tokens': 1973,               # 总token数 类型:int 值来源:模型计算
                    'completion_tokens_details': None, # 输出token详情 类型:None 值来源:无数据
                    'prompt_tokens_details': {          # 输入token详情 类型:dict 值来源:模型返回
                        'audio_tokens': None,           # 音频token 类型:None 值来源:无音频
                        'cached_tokens': 0              # 缓存token 类型:int 值来源:无缓存
                    }
                },
                'model_provider': 'openai',       # 模型厂商 类型:str 值来源:兼容协议标识
                'model_name': 'qwen-plus',         # 模型名称 类型:str 值来源:自定义配置
                'system_fingerprint': None,        # 系统指纹 类型:None 值来源:无数据
                'id': 'chatcmpl-53c20f3e-9b99-9b50-ad4f-843bfa7e21cc',  # 对话ID 类型:str 值来源:接口生成
                'finish_reason': 'tool_calls',    # 结束原因 类型:str tool_calls=调用工具 值来源:模型返回
                'logprobs': None                   # 日志概率 类型:None 值来源:无数据
            },
            id='lc_run--019e9cd3-b281-77d2-8c95-4933cfeafde7-0',  # 运行ID 类型:str 值来源:系统生成
            tool_calls=[  # 工具调用列表 类型:list 值来源:大模型指令
                {
                    'name': 'queryOrderStatus',  # 工具名称 类型:str 值来源:自定义工具名
                    'args': {'__arg1': '1024'},  # 工具参数 类型:dict 值来源:大模型解析生成
                    'id': 'call_d5306434579d4e93b3ad37',  # 调用ID 类型:str 值来源:系统生成
                    'type': 'tool_call'          # 类型标识 类型:str 固定值tool_call
                }
            ],
            invalid_tool_calls=[],  # 无效工具调用 类型:list 空=无无效调用 值来源:无错误
            usage_metadata={  # 使用元数据 类型:dict 值来源:统计生成
                'input_tokens': 1948,              # 输入token 类型:int 值来源:模型统计
                'output_tokens': 25,               # 输出token 类型:int 值来源:模型统计
                'total_tokens': 1973,              # 总token 类型:int 值来源:模型统计
                'input_token_details': {'cache_read': 0},  # 输入详情 类型:dict 值来源:无缓存
                'output_token_details': {}         # 输出详情 类型:dict 值来源:无数据
            }
        )
    ],
    # 键:__interrupt__ 作用:工具调用中断 类型:list 值来源:LangChain安全机制
    '__interrupt__': [
        Interrupt(  # 中断对象 类型:Interrupt 值来源:需要审批工具调用
            value={  # 中断值 类型:dict 值来源:系统生成
                'action_requests': [  # 操作请求 类型:list 值来源:工具调用申请
                    {
                        'name': 'queryOrderStatus',  # 工具名 类型:str 值来源:自定义工具
                        'args': {'__arg1': '1024'},  # 参数 类型:dict 值来源:模型解析
                        'description': 'Tool execution requires approval\n\nTool: queryOrderStatus\nArgs: {\'__arg1\': \'1024\'}'  # 描述 类型:str 固定提示
                    }
                ],
                'review_configs': [  # 审批配置 类型:list 值来源:系统设置
                    {
                        'action_name': 'queryOrderStatus',  # 操作名 类型:str 值来源:工具名
                        'allowed_decisions': ['approve', 'reject']  # 允许操作 类型:list 固定值:同意/拒绝
                    }
                ]
            },
            id='5ce1059b62115668f1e1ea4c98a83ca9'  # 中断ID 类型:str 值来源:系统生成
        )
    ]
}

增加输入同意或拒绝功能

效果图:如果输入reject拒绝了,大模型也会回答,只不过它没有使用工具自己乱编的答案

python 复制代码
# 作用:创建智能代理对象
# 来源:langchain.agents模块
from langchain.agents import create_agent
# 作用:Tavily联网搜索工具
# 来源:langchain_tavily第三方库
from langchain_tavily import TavilySearch
# 作用:兼容openai协议调用大模型
# 来源:langchain_openai库
from langchain_openai import ChatOpenAI
# 作用:自定义函数封装为工具
# 来源:langchain_core.tools
from langchain_core.tools import Tool
# 作用:系统内置,环境变量操作
# 来源:python内置os
import os
# 作用:读取.env配置文件
# 来源:python-dotenv库
from dotenv import load_dotenv
# 作用:人工审批中间件,控制工具调用中断
# 来源:langchain.agents.middleware
from langchain.agents.middleware import HumanInTheLoopMiddleware
# 作用:内存存储会话历史、断点数据
# 来源:langgraph.checkpoint.memory
from langgraph.checkpoint.memory import InMemorySaver
# 作用:断点恢复指令对象
# 来源:langgraph.types
from langgraph.types import Command

# 作用:加载项目.env环境配置
# 入参:无
# 可传:无
# 值来源:固定调用
load_dotenv()

# 作用:自定义函数,根据订单编号查订单状态
# 入参:order_id 字符串
# 可传:任意订单编号,仅"1024"命中数据
# 值来源:大模型从用户问题提取参数
def query_order_status(order_id):
    if order_id == "1024":
        return "订单 1024 的状态是:已发货,预计送达时间是 3-5 个工作日。"
    else:
        return f"未找到订单 {order_id} 的信息,请检查订单号是否正确。"

# 作用:自定义函数,查询指定公司退款规则
# 入参:company_name 字符串
# 可传:任意公司名,仅"tom"有返回内容
# 值来源:大模型从用户问题提取参数
def company_refund_policy(company_name):
    print(company_name)
    if company_name == "tom":
        return "tom公司的退款政策是:在购买后7天内可以申请全额退款,需提供购买凭证。"
    else:
        print('输入有误')

# 作用:自定义函数,查询tom年龄
# 入参:name 字符串
# 可传:任意姓名,仅"tom"正常返回年龄
# 值来源:大模型从用户问题提取参数
def get_age(name):
    if name == "tom":
        print(name)
        return "我的年龄是56岁!"
    else:
        print('输入有误')

# 作用:组装全部可用工具列表,提供给agent调用
# 入参:工具实例集合
# 可传:搜索工具、Tool封装自定义函数
# 值来源:下方逐个定义工具
tools = [
    # 作用:在线联网搜索工具
    # max_results:int,单次返回结果条数,可传1~5,来源自定义
    # tavily_api_key:str,搜索密钥,可传官网申请key,来源.env里TAVILY_API_KEY
    TavilySearch(max_results=1, tavily_api_key=os.getenv("TAVILY_API_KEY")),
    # 作用:订单查询工具封装
    # name:str,工具标识名,自定义命名
    # func:绑定执行函数,来源上方query_order_status
    # description:str,工具用途说明,给大模型识别
    # args:字典,参数释义
    Tool(
        name="queryOrderStatus",
        func=query_order_status,
        description="根据订单ID查询订单状态",
        args={"order_id": "订单的ID"}
    ),
    # 作用:退款政策查询工具封装
    Tool(
        name="companyRefundPolicy",
        func=company_refund_policy,
        description="查询某某公司退款政策详细内容",
        args={"company_name": "公司名称"}
    ),
    # 作用:年龄查询工具封装
    Tool(
        name="getAge",
        func=get_age,
        description="查询tom年龄大小",
        args={"name": "查询tom年龄大小"}
    ),
]

# 作用:初始化通义千问大模型实例
# api_key:str,阿里密钥,来源.env DASHSCOPE_API_KEY
# base_url:str,兼容接口地址,固定官方地址
# model:str,模型名称,可选qwen-plus/qwen-turbo等,来源官方模型名
llm = ChatOpenAI(api_key=os.getenv("DASHSCOPE_API_KEY"),
                 base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
                 model='qwen-plus')

# 作用:实例化智能代理
# model:大模型对象,来源上方llm变量
# tools:工具列表,来源上方tools变量
# system_prompt:str,系统约束提示词,来源自定义配置
# middleware:列表,存放审批中间件
# checkpointer:会话存储器实例,InMemorySaver()内存存储
agent = create_agent(
    model=llm,
    tools=tools,
    system_prompt="你是一个客服助手,使用工具回答问题。传递给工具的内容必须是准确的json数据不结尾的括号多加一个,如果是字符串数据必须和输入的保持一致要完整,不是篡改**重要规则**, ",
    # 使用中间件
    middleware=[
            # 作用:人工审批中间件,所有工具调用触发人工确认
            # interrupt_on:字典,key=工具名,value=审批选项,固定approve/reject
            HumanInTheLoopMiddleware(
                interrupt_on={
                    'tavily_search':{'allowed_decisions':["approve", "reject"]},
                    "queryOrderStatus": {'allowed_decisions': ['approve', 'reject']},
                    "companyRefundPolicy": {'allowed_decisions': ['approve', 'reject']},
                    "getAge": {'allowed_decisions': ['approve', 'reject']},
                }
        )
    ],
    # 让 agent 代理具备"记住对话历史 + 支持中断后继续"的能力
    checkpointer=InMemorySaver(),
)

# 作用:测试提问集合
# 入参:字符串问题
# 可传:任意用户问题
# 值来源:自定义测试用例
queries = [
    "请问订单1024的状态是什么?",
    "请问tom公司退款政策是什么?",
    "2024年谁胜出了美国总统的选举"
]

# 作用:会话配置,thread_id标记独立会话,同一个id保留上下文
# configurable:固定key,thread_id自定义字符串
# 可传:任意自定义id字符串
# 值来源:自定义设置
config = {"configurable": {"thread_id": "some_other_id_123"}}

# 作用:循环逐个执行用户提问
for que in queries:
    print('客户提问:' + que)
    # 作用:组装agent规定输入格式
    inputs = {"messages": [{"role": "user", "content": que}]}
    # 作用:首次调用agent,触发工具审批中断
    result = agent.invoke(inputs, config=config)
    # 作用:从中断结果取出待审批工具名称
    tool_name = result['__interrupt__'][0].value['action_requests'][0]['name']
    # 作用:控制台手动输入审批指令,只能approve/reject
    app_or_reject = input("请确认调用{}是否同意(approve or reject):".format(tool_name))
    # 作用:构造恢复指令,携带审批结果继续运行agent
    # Command:恢复对象,resume传入审批结果字典
    res = agent.invoke(
        Command(resume={'decisions': [{'type': app_or_reject}]}),
        config=config
    )
    # 作用:打印最终大模型回答
    print(res['messages'][-1].content)

相关推荐
孟俊宇-MJY1 小时前
CSDN AI数字营销内容创作功能测评
大数据·人工智能
网络研究院1 小时前
AI安全格局:前沿模型、智能体AI和AI编码工具如何重塑网络安全与关键基础设施韧性
网络·人工智能·安全·模型·威胁
装不满的克莱因瓶1 小时前
从梯度下降到 Adam 优化器:掌握神经网络参数优化的核心原理
人工智能·python·深度学习·神经网络·机器学习·计算机视觉·ai
maosheng11461 小时前
基于AI 文本生成的自动化Linux 运维文档系统
运维·人工智能·自动化
cxr8281 小时前
分享新疆应急决策大脑功能特性简报
人工智能·应急响应管理
Raink老师1 小时前
【AI面试临阵磨枪-98】前端如何展示多模态流式输出:文字打字机 + 图片渐进 + 音频播放?
前端·人工智能·面试
Jelena157795857921 小时前
主流电商平台(淘宝/1688/京东/拼多多)商品比价与数据分析实战指南
大数据·人工智能·数据分析
ShareBeHappy_Qin1 小时前
AI —— Agent相关概念-1
人工智能·ai·agent
小雨下雨的雨1 小时前
鸿蒙PC Electron框架实现流体气泡模拟器
前端·人工智能·算法·华为·electron·鸿蒙