AI Agent实战 - LangChain+Playwright构建火车票查询Agent

本篇文章将带你一步步构建一个智能火车票查询 Agent:你只需要输入自然语言指令,例如:

“帮我查一下6月15号从上海到南京的火车票”

Agent就能自动理解你的需求并使用 Playwright 打开 12306 官网查询前 10 条车次信息,然后汇总结果。

通过这个完整示例,希望可以帮助大家入门AI Agent开发,掌握如何结合大语言模型、LangChain 工具调用能力以及Playwright,打造一个可以执行任务的智能Agent。那我们开始吧

项目初始化

现在开始进行具体项目搭建,项目整体结构如下:

train_ticket_agent
├── core/                         # 核心逻辑模块,MyAgent类封装
│   └── agent.py
├── main.py                       # 入口程序,运行 Agent
├── prompts/                      # 存放提示词模板
│   ├── final_prompt.txt
│   └── task_prompt.txt
├── requirements.txt              # 依赖列表
├── tools/                        # 工具模块,供 Agent 调用
│   ├── finish.py                 # Finish 工具(占位结束)
│   └── train_ticket_query.py     # 火车票查询工具,调用 Playwright 查询 12306
└── utils/                        # 通用工具代码└── ticket_query_scraper.py   # Playwright 查询 12306 官网,封装成可复用方法
  • core/ → 封装 MyAgent 核心智能体逻辑
  • prompts/ → 任务提示词(task_prompt)+ 完成提示词(final_prompt)
  • tools/ → 所有可调用工具(火车票查询 / 结束任务)
  • utils/ ticket_query_scraper.py → Playwright爬取12306封装
  • main.py → 主入口
  • requirements.txt → 项目依赖管理

安装运行环境

1 . 创建虚拟环境

python -m venv .venv
source .venv/bin/activate  # Mac/Linux
# 或
.venv\\Scripts\\activate     # Windows

2 . 安装依赖

requirements.txt内容如下

langchain==0.3.25
python-dotenv~=1.1.0
langchain-experimental==0.3.4
pydantic~=2.10.3
playwright~=1.52.0
pypinyin~=0.54.0

安装依赖包

pip install -r requirementst.txt

安装Playwright:

playwright install

3 . 设置openai的api key

在这个示例中使用的大模型是gpt-3.5,需要在项目中配置API Key,当然大家也可以使用其他大模型

在项目根目录下创建一个 .env 文件(若尚未存在),添加以下内容:

OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

工具Tools开发

自动查询火车票工具

我们首先的第一个任务是接收用户的自然语言输入比如 “帮我查一下 6 月 15 号从上海到南京的火车票”,然后将用户的需求解析为结构化的输入(出发地、目的地、日期、时间段),以便工具可以使用Playwright实时访问12306查询页面,提取前 10 条火车票信息,整理成结构化的 JSON 结果返回给用户。

utils/train_ticket_scraper.py

import asyncio
from typing import Listfrom playwright.async_api import async_playwright
from pypinyin import lazy_pinyin, Styleasync def select_city(page, selector: str, city_name: str):initials = get_pinyin(city_name)await page.click(selector)for c in initials:await page.keyboard.press(c)await page.wait_for_timeout(100)await page.wait_for_timeout(500)await page.keyboard.press("Enter")async def extract_train_data(page):rows = await page.query_selector_all("#queryLeftTable tr.bgc")results = []for row in rows[:10]:  # 只取前10条train_info = {}# 车次编号train_number_el = await row.query_selector("div.train a.number")train_info["train_number"] = (await train_number_el.text_content()).strip() if train_number_el else "-"# 出发地与到达地station_els = await row.query_selector_all("div.cdz strong")from_station_el = station_els[0] if len(station_els) > 0 else Noneto_station_el = station_els[1] if len(station_els) > 1 else Nonetrain_info["origin"] = (await from_station_el.text_content()).strip() if from_station_el else "-"train_info["destination"] = (await to_station_el.text_content()).strip() if to_station_el else "-"# 出发时间与到达时间departure_time_el = await row.query_selector("div.cds .start-t")arrival_time_el = await row.query_selector("div.cds .color999")train_info["departure_time"] = (await departure_time_el.text_content()).strip() if departure_time_el else "-"train_info["arrival_time"] = (await arrival_time_el.text_content()).strip() if arrival_time_el else "-"# 历时duration_el = await row.query_selector("div.ls strong")train_info["duration"] = (await duration_el.text_content()).strip() if duration_el else "-"# 各座位类型seat_cells = await row.query_selector_all("td")try:train_info["business_seat"] = (await seat_cells[1].inner_text()).strip()train_info["first_class_seat"] = (await seat_cells[3].inner_text()).strip()train_info["second_class_seat"] = (await seat_cells[4].inner_text()).strip()except IndexError:train_info["business_seat"] = "-"train_info["first_class_seat"] = "-"train_info["second_class_seat"] = "-"results.append(train_info)return resultsdef get_pinyin(text: str) -> str:"""将中文字符串转换为拼音"""return ''.join(lazy_pinyin(text, style=Style.NORMAL))async def extract_train_data_with_browser(origin: str, destination: str, date: str) -> List[dict]:async with async_playwright() as p:browser = await p.chromium.launch(headless=False)  # 设置为 True 可无头运行context = await browser.new_context()page = await context.new_page()# 打开 12306 首页await page.goto("<https://www.12306.cn/index/>")# 输入查询条件await select_city(page, "#fromStationText", origin)await select_city(page, "#toStationText", destination)# 填写出发日期(注意:必须是未来的日期,格式:YYYY-MM-DD)await page.fill('#train_date', date)# 等待新页面打开async with context.expect_page() as new_page_info:await page.click('#search_one')result_page = await new_page_info.value  # 获取新打开的 tabawait result_page.wait_for_load_state('domcontentloaded')await result_page.wait_for_selector("#queryLeftTable", timeout=10000)result = await extract_train_data(result_page)print("查询结果:")for train in result:print(train)print("查询完成")await browser.close()return {"message": "查询成功","results": result}

✅ 通过Playwright从12306爬取真实的火车票信息:

  • extract_train_data_with_browser启动浏览器,输入查询条件,提取结果。
  • extract_train_data 负责从结果页面中提取前10条火车票数据,整理成JSON格式。

tools/train_ticket_query.py

from typing import List
from langchain_core.tools import StructuredTool
import asyncio
from utils.ticket_query_scraper import extract_train_data_with_browser  # 改造你的 Playwright 脚本成一个可复用函数def search_train_ticket(origin: str,destination: str,date: str,
) -> List[dict]:"""按条件查询火车票"""async def _run():return await extract_train_data_with_browser(origin, destination, date)# 用 asyncio 运行异步逻辑result = asyncio.run(_run())return resultsearch_train_ticket_tool = StructuredTool.from_function(func=search_train_ticket,name="查询火车票",description="调用12306官网,真实查询火车票"
)

✅ 将playwright工具封装到LangChain Tool中:

  • search_train_ticket_tool使用 StructuredTool.from_function封装 Python函数,供Agent调用。
  • LangChain Agent调用这个工具时,能自动传入origin / destination / date参数,调用封装了playwright的函数并获取火车票结果。

完成任务工具 tools/finish.py

from langchain_core.tools import StructuredTooldef finish_placeholder():"""用于表示任务完成的占位符工具"""return Nonefinish_tool = StructuredTool.from_function(func=finish_placeholder,name="FINISH",description="表示任务完成"
)

Prompt提示词设计

现在编写提示词让大模型可以根据任务内容和上下文记忆自己去选择使用什么工具,需要两个prompt

  • 任务提示词模板task_prompt: 用于指导大模型按格式输出
  • 任务完成提示词final_prompt: Agent任务完成后调用此提示词生成最终回复

任务提示词模板(task_prompt.txt)

你是强大的AI火车票助手,可以使用工具与指令查询并购买火车票。你的任务是:
{task_description}你可以使用以下工具或指令,它们又称为动作(Actions):
{tools}当前的任务执行记录如下:
{memory}请根据任务描述和历史记录思考你下一步的行动。请按照以下格式输出:任务:你收到的需要执行的任务
思考:你如何理解这个任务?下一步该怎么做?
Action: 要执行的工具名称(必须是上面列出的工具名之一)
Action Input: 调用该工具所需的参数
{format_instructions}示例格式:
{{"name": "查询火车票","args": {{"origin": "北京","destination": "上海","date": "2024-10-30"}}
}}⚠️ 特别说明:- 如果你调用工具后观察到的结果中包含以下字段:{{"message": "查询成功"}}说明任务已经成功完成,请在下一步输出以下内容表示任务完成:{{"name": "FINISH","args": {{}}}}- 请确保你的输出是符合JSON格式的结构化内容,不能包含自然语言。

这个prompt将接收以下的参数

变量作用
{task_description}当前用户请求,如“帮我查一下 6 月 15 号从上海到南京的火车票
{tools}传入工具列表以便大模型可以选择,这些就是之前我们开发的工具
{memory}上下文记忆(思考 + 工具执行记录)
{format_instructions}用于约束输出为合法 JSON(否则 Pydantic 会报错)

💡**调试建议:**在调试时模型经常会不听话输出非Json的文本,导致解析失败(如 OutputParserException: Invalid json output 报错)。使用 {format_instructions} 可强制模型生成结构化 JSON 输出,是解决这类问题的关键。

任务完成提示词模板(final_prompt.txt)

你的任务是:
{task_description}以下是你之前的思考过程和使用工具与外部资源交互的结果:
{memory}你已经完成了任务。现在请根据上述交互结果,总结出本次任务的最终答案。请遵循以下规则输出结果:
- 请优先参考 Observation(工具的返回结果)来组织信息,不需要分析思考内容。
- 如果任务是火车票查询,请汇总返回的车次列表、出发/到达站、时间、座位情况,整理成清晰可读的文本。
- 遍历所有results列表中的项目,提取有用信息。完整罗列出来,不要省略、不仅仅选前几个结果。

在完成查询后让大模型帮忙总结并汇总出车次结果


🤖 MyAgent 类实现

MyAgent 是智能火车票助手的核心类,它主要的功能包括

  • 管理大模型调用
  • 管理工具调用
  • 维护上下文记忆
  • 实现推理主流程

先上完整代码

# core/agent.pyimport json
import sys
from typing import Optional, Tuple, Dict, Any
from uuid import UUIDfrom pydantic import ValidationError, BaseModel, Field
from langchain.memory import ConversationTokenBufferMemory
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import PydanticOutputParser, StrOutputParser
from langchain_core.language_models import BaseChatModel
from langchain_core.outputs import GenerationChunk, ChatGenerationChunk, LLMResult
from langchain_core.callbacks import BaseCallbackHandlerfrom langchain.tools.render import render_text_descriptionclass ActionModel(BaseModel):name: str = Field(description="工具或指令名称")args: Optional[Dict[str, Any]] = Field(description="工具或指令参数,由参数名称和参数值组成")class MyPrintHandler(BaseCallbackHandler):"""自定义 CallbackHandler,用于打印 LLM 推理过程"""def on_llm_new_token(self,token: str,*,chunk: Optional[GenerationChunk] = None,run_id: UUID,parent_run_id: Optional[UUID] = None,**kwargs: Any,) -> Any:sys.stdout.write(token)sys.stdout.flush()def on_llm_end(self, response: LLMResult, **kwargs: Any) -> Any:sys.stdout.write("\\n")sys.stdout.flush()return responseclass MyAgent:def __init__(self,llm: BaseChatModel,tools: list,prompt: PromptTemplate,final_prompt: str,max_thought_steps: Optional[int] = 3,):self.llm = llm# Convert tool list to dict for fast lookup by nameself.tools = {tool.name: tool for tool in tools}self.max_thought_steps = max_thought_stepsself.output_parser = PydanticOutputParser(pydantic_object=ActionModel)self.final_prompt = PromptTemplate.from_template(final_prompt)self.llm_chain = prompt | self.llm | StrOutputParser()self.verbose_printer = MyPrintHandler()self.agent_memory = self.init_memory()def init_memory(self):memory = ConversationTokenBufferMemory(llm=self.llm, max_token_limit=4000)memory.save_context({"input": "\\ninit"}, {"output": "\\n开始"})return memorydef run(self, task_description: str) -> str:print("开始执行任务...")thought_step_count = 0agent_memory = self.agent_memorywhile thought_step_count < self.max_thought_steps:print(f"思考步骤 {thought_step_count + 1}")action, response = self.__step(task_description, agent_memory)# 如果 Action 是 FINISH,则结束if action.name == "FINISH":final_chain = self.final_prompt | self.llm | StrOutputParser()reply = final_chain.invoke({"task_description": task_description,"memory": agent_memory})print(f"----\\n最终回复:\\n{reply}")return reply# 执行动作action_result = self.__exec_action(action)# 更新记忆self.update_memory(response, action_result)thought_step_count += 1if thought_step_count >= self.max_thought_steps:# 如果思考步数达到上限,返回错误信息print("任务未完成!")return "任务未完成!"def __step(self, task_description, memory) -> Tuple[ActionModel, str]:response = ""for s in self.llm_chain.stream({"task_description": task_description,"memory": memory}, config={"callbacks": [self.verbose_printer]}):response += sprint(f"----\\nResponse:\\n{response}")action = self.output_parser.parse(response)return action, responsedef __exec_action(self, action: ActionModel) -> str:if not action or not action.name:print("未提供有效的动作或工具名称")return "未提供有效的动作或工具名称"tool = self.tools.get(action.name)if not tool:print(f"未找到名称为 {action.name} 的工具")return f"未找到名称为 {action.name} 的工具"try:return tool.run(action.args)except ValidationError as e:return f"参数校验错误: {str(e)}, 参数: {action.args}"except Exception as e:return f"执行出错: {str(e)}, 类型: {type(e).__name__}, 参数: {action.args}"def update_memory(self, response, observation):self.agent_memory.save_context({"input": response},{"output": "\\n返回结果:\\n" + str(observation)})

初始化init方法介绍

def __init__(self,llm: BaseChatModel,tools: list,prompt: PromptTemplate,final_prompt: str,max_thought_steps: Optional[int] = 3,
):self.llm = llm# 将工具列表转为 dict 方便按 name 快速查找self.tools = {tool.name: tool for tool in tools}self.max_thought_steps = max_thought_stepsself.output_parser = PydanticOutputParser(pydantic_object=ActionModel)self.final_prompt = PromptTemplate.from_template(final_prompt)self.llm_chain = prompt | self.llm | StrOutputParser()self.verbose_printer = MyPrintHandler()self.agent_memory = self.init_memory()

init方法的参数和说明如下

参数说明
llm大语言模型实例,表示需要使用大模型接口
tools可调用的工具列表,需为StructuredTool 对象
max_thought_steps智能体最多思考几轮(避免死循环)
output_parser通过ActionModel将LLM 输出结构化为一个 Action(name=..., args=...) 对象
self.llm_chainLangChain中的Chain管道式写法的,表示将prompt调用大模型后再将respone内容使用StrOutputParser处理输出
final_prompt完成任务时的提示词
verbose_printerMyPrintHandler 是一个自定义的 CallbackHandler,用于实时输出 LLM 的推理过程
agent_memory初始化智能体Agent的记忆上下文

初始化记忆

Agent 需要具备“上下文记忆”能力,以便在多轮推理过程中保留每一步的思考与执行记录。这里使用ConversationTokenBufferMemory,它能够根据token限制保留最新的上下文信息。

def init_memory(self):memory = ConversationTokenBufferMemory(llm=self.llm, max_token_limit=4000)memory.save_context({"input": "\\ninit"}, {"output": "\\n开始"})return memory

Agent推理主流程 - run

run是Agent的核心方法,执行任务完整的思考和工具调用的过程,主要步骤包括:

  1. 获取智能体Agent的上下文记忆agent_memory

  2. 执行推理思考的循环

    Agent会在限定的思考轮次内不断尝试解决任务,直到完成或达到最大步数为止。在每一轮的思考中的步骤如下:

    • 调用__step(), 把 task描述和上下文记忆memory传入prompt,大模型根据记忆和任务描述返回下一步需要执行的Action
    • 调用__exec_action函数,根据Action执行对应的工具
    • 将工具返回的结果更新到记忆中
    • 重复进入下一轮思考
  3. 生成最终回复

    如果Agent 成功完成任务或达到最大轮次后会执行finish的工具,并以比较友好的自然语言回复给用户。


运行整体流程

前面我们已经完成以下部分:

  • ✅ 工具开发(查询、完成)
  • ✅ 编写Prompt(task_prompt、final_prompt)
  • ✅ 编写MyAgent类

现在需要验证整体流程是否串联成功。main.py示例代码:

import jsonfrom dotenv import load_dotenv
from langchain_community.chat_models import ChatOpenAI
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.tools import StructuredTool, render_text_description
from core.agent import MyAgent, ActionModel
from tools.train_ticket_query import search_train_ticket_tool
from tools.finish import finish_toolload_dotenv()if __name__ == "__main__":tools = [search_train_ticket_tool, finish_tool]with open("prompts/task_prompt.txt", "r", encoding="utf-8") as f:prompt_text = f.read()with open("prompts/final_prompt.txt", "r", encoding="utf-8") as f:final_prompt_text = f.read()# 构建提示词模板(PromptTemplate) ← 你在 main.py 中做这件事parser = PydanticOutputParser(pydantic_object=ActionModel)prompt = PromptTemplate.from_template(prompt_text).partial(tools=render_text_description(tools),format_instructions=json.dumps(parser.get_format_instructions(), ensure_ascii=False))my_agent = MyAgent(llm=ChatOpenAI(model="gpt-3.5-turbo", temperature=0),tools=tools,prompt=prompt,final_prompt=final_prompt_text,)task = "帮我买25年6月10日早上去南京的火车票"reply = my_agent.run(task)

运行结果示意

运行main.py 后,可以看到类似下面这样的流程打印:

开始执行任务...
思考步骤 1
{"name": "查询火车票","args": {"origin": "上海","destination": "南京","date": "2025-06-10"}
}
----
Response:
{"name": "查询火车票","args": {"origin": "上海","destination": "南京","date": "2025-06-10"}
}
查询结果:
{'train_number': 'G7070', 'origin': '上海', 'destination': '南京南', 'departure_time': '20:46', 'arrival_time': '22:48', 'duration': '02:02', 'business_seat': '无', 'first_class_seat': '12', 'second_class_seat': '有'}
{'train_number': 'G7098', 'origin': '上海', 'destination': '南京', 'departure_time': '21:05', 'arrival_time': '22:59', 'duration': '01:54', 'business_seat': '--', 'first_class_seat': '18', 'second_class_seat': '有'}
{'train_number': 'D182', 'origin': '上海松江', 'destination': '南京', 'departure_time': '21:22', 'arrival_time': '00:31', 'duration': '03:09', 'business_seat': '--', 'first_class_seat': '--', 'second_class_seat': '候补'}
{'train_number': 'G7112', 'origin': '上海虹桥', 'destination': '南京', 'departure_time': '21:35', 'arrival_time': '23:15', 'duration': '01:40', 'business_seat': '--', 'first_class_seat': '有', 'second_class_seat': '有'}
{'train_number': 'G7068', 'origin': '上海', 'destination': '南京', 'departure_time': '21:50', 'arrival_time': '23:23', 'duration': '01:33', 'business_seat': '--', 'first_class_seat': '20', 'second_class_seat': '有'}
{'train_number': 'K8482', 'origin': '上海', 'destination': '南京', 'departure_time': '22:10', 'arrival_time': '01:27', 'duration': '03:17', 'business_seat': '--', 'first_class_seat': '--', 'second_class_seat': '--'}
{'train_number': 'K1048', 'origin': '上海', 'destination': '南京', 'departure_time': '22:23', 'arrival_time': '02:08', 'duration': '03:45', 'business_seat': '--', 'first_class_seat': '--', 'second_class_seat': '--'}
{'train_number': 'K850', 'origin': '上海', 'destination': '南京', 'departure_time': '23:21', 'arrival_time': '04:34', 'duration': '05:13', 'business_seat': '--', 'first_class_seat': '--', 'second_class_seat': '--'}
{'train_number': 'K1506', 'origin': '上海', 'destination': '南京', 'departure_time': '23:40', 'arrival_time': '03:26', 'duration': '03:46', 'business_seat': '--', 'first_class_seat': '--', 'second_class_seat': '--'}
查询完成
思考步骤 2
{"name": "FINISH","args": {}
}
----
Response:
{"name": "FINISH","args": {}
}
----
最终回复:
根据查询结果,2025年6月10日去南京的火车票如下:
1. 列车编号:G7070- 出发站:上海- 到达站:南京南- 出发时间:20:46- 到达时间:22:48- 历时:02小时02分钟- 商务座:无- 一等座:12张- 二等座:有2. 列车编号:G7098- 出发站:上海- 到达站:南京- 出发时间:21:05- 到达时间:22:59- 历时:01小时54分钟- 商务座:--- 一等座:18张- 二等座:有3. 列车编号:D182- 出发站:上海松江- 到达站:南京- 出发时间:21:22- 到达时间:00:31- 历时:03小时09分钟- 商务座:--- 一等座:--- 二等座:候补4. 列车编号:G7112- 出发站:上海虹桥- 到达站:南京- 出发时间:21:35- 到达时间:23:15- 历时:01小时40分钟- 商务座:--- 一等座:有- 二等座:有5. 列车编号:G7068- 出发站:上海- 到达站:南京- 出发时间:21:50- 到达时间:23:23- 历时:01小时33分钟- 商务座:--- 一等座:20张- 二等座:有6. 列车编号:K8482- 出发站:上海- 到达站:南京- 出发时间:22:10- 到达时间:01:27- 历时:03小时17分钟- 商务座:--- 一等座:--- 二等座:--7. 列车编号:K1048- 出发站:上海- 到达站:南京- 出发时间:22:23- 到达时间:02:08- 历时:03小时45分钟- 商务座:--- 一等座:--- 二等座:--8. 列车编号:K850- 出发站:上海- 到达站:南京- 出发时间:23:21- 到达时间:04:34- 历时:05小时13分钟- 商务座:--- 一等座:--- 二等座:--9. 列车编号:K1506- 出发站:上海- 到达站:南京- 出发时间:23:40- 到达时间:03:26- 历时:03小时46分钟- 商务座:--- 一等座:--- 二等座:--

小结

通过上面我们完成了一个完整的 LangChain + ReAct 智能体实践案例,具备以下能力:

✅ 能理解用户自然语言请求

✅ 能通过 Prompt 引导大模型选择合适工具

✅ 能自动完成工具调用、记忆更新、迭代推理

✅ 最终输出结果反馈给用户

Github仓库地址

https://github.com/bridgeshi85/train-ticket-agent

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/83737.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ的交换机和队列概念

&#x1f3ea; 场景&#xff1a;一个外卖平台的后台系统 假设你开了一家在线外卖平台&#xff1a; 饭店是消息的生产者&#xff08;Producer&#xff09;顾客是消息的消费者&#xff08;Consumer&#xff09;你开的外卖平台就是RabbitMQ消息系统 &#x1f501; 第一部分&…

德国马克斯·普朗克数学研究所:几何朗兰兹猜想

2025年科学突破奖 4月5日在美国洛杉矶揭晓&#xff1a;数学突破奖&#xff1a;德国马克斯普朗克数学研究所&#xff1a;几何朗兰兹猜想 德国马克斯普朗克数学研究所&#xff08;Max Planck Institute for Mathematics, MPIM&#xff09;在几何朗兰兹猜想的研究中扮演了核心角色…

TerraFE 脚手架开发实战系列(一):项目架构设计与技术选型

TerraFE 脚手架开发实战系列&#xff08;一&#xff09;&#xff1a;项目架构设计与技术选型 前言 在前端开发中&#xff0c;项目初始化往往是一个重复且繁琐的过程。每次新建项目都需要配置 webpack、安装依赖、设置目录结构等&#xff0c;这些重复性工作不仅浪费时间&#…

准确--CentOS 7.9在线安装docker

一、安装Docker前的准备工作 操作系统版本为CentOS 7.9&#xff0c;内核版本需要在3.10以上。确保能够连通互联网&#xff0c;为避免网络异常&#xff0c;建议关闭Linux的防火墙&#xff08;生产环境下请根据实际情况设置防火墙出入站规则&#xff09;。 # 查看内核版本 sudo…

中兴B860AV1.1强力降级固件包

中兴B860AV1.1强力降级固件包 关于中兴b860av1.1顽固盒子降级教程终极版 将附件解压好以后&#xff0c;准备一个8G以下的U盘重新格式化为FAT32格式后&#xff0c;并插入电脑 将以下文件及文件夹一同复制到优盘主目录下&#xff08;见下图&#xff09; 全选并复制到U盘主目录下&…

nacos-作为注册中心与springcloud整合(三)

前一篇文章nacos-简介和初体验&#xff08;一&#xff09;我们已经在服务器部署了nacos应用了。 在另外一篇文章中nacos-作为配置中心与springcloud整合&#xff08;二&#xff09;已经作为配置中心整合到springcloud 接下来让我们尝试把nacos作为注册中心和springcloud中整合&…

Seata的TC(事务协调器)高可用如何实现?

Seata的TC&#xff08;事务协调器&#xff09;确实运行在Seata服务进程中&#xff0c;其高可用实现和宕机恢复主要通过以下机制实现&#xff1a; 一、高可用架构 集群部署 多TC节点组成集群&#xff0c;通过注册中心&#xff08;如Nacos&#xff09;实现服务发现采用Raft协议实…

Mac安装docker desktop

一、背景 最近在学习Spring AI&#xff0c;于是在GitHub上找了个开源项目&#xff0c;个人觉得还是比较适合有Java基础和AI基础的同学学习的。GitHub地址如下&#xff1a; https://github.com/qifan777/dive-into-spring-ai 但是看了下运行环境需要 MySQL 8 Redis-Stack n…

【算法深练】二分答案:从「猜答案」到「精准求解」的解题思路

目录 前言 二分求最小值 1283. 使结果不超过阈值的最小除数 2187. 完成旅途的最少时间 1011. 在 D 天内送达包裹的能力 875. 爱吃香蕉的珂珂 3296. 移山所需的最少秒数 475. 供暖器 2594. 修车的最少时间 1482. 制作 m 束花所需的最少天数 3048. 标记所有下标的最早秒…

基于RK3588,飞凌教育品牌推出嵌入式人工智能实验箱EDU-AIoT ELF 2

在AIoT技术驱动产业变革的浪潮中&#xff0c;嵌入式人工智能已成为工业物联网、智慧交通、智慧医疗等领域创新突破的关键引擎。飞凌嵌入式教育品牌ElfBoard立足产业前沿&#xff0c;重磅推出嵌入式人工智能实验箱EDU-AIoT ELF 2&#xff0c;以“软硬协同、产教融合”为设计理念…

51单片机-IO扩展模块 pcf8575

PCF8575介绍 PCF8575 是 NXP&#xff08;原飞利浦半导体&#xff09;生产的一款通用 IC 总线 I/O 扩展器芯片&#xff0c;主要用于微控制器&#xff08;如 Arduino、STM32 等&#xff09;的 I/O 端口扩展。 主要特性 16位并行 I/O 端口&#xff1a;可以配置为输入或输出 IC 总…

Python3 学习(菜鸟)-02基本数据类型

1.多变量赋值 多变量被赋予相同的数值 多变量被赋予不同的数值 2.数值运算 除法 /&#xff1a;返回一个浮点数 除法 //&#xff1a;返回一个整数 3.列表 加号和星号 加号 是列表连接运算符 星号 * 是重复操作 list [ abcd, 786 , 2.23, runoob, 70.2 ] # 定义一个…

『uniapp』搜索功能+商品列表滚动效果(详细图文注释)

目录 预览效果准备工作代码分析与思路1. 页面结构主容器:`menber-container`搜索框:`u-search-inner`菜单:`u-menu-wrap`2. 数据模型`data()` 中的数据定义:3. 生命周期`onLoad(options)``onReady()``mounted()`4. 方法`search()``searchClear()``swichMenu(index)``getElRe…

微服务--消息队列mq

1. mq简介 消息队列是分布式系统中的异步通信中间件&#xff0c;采用"生产者-消费者"模型实现服务间解耦通信 核心作用 服务解耦异步处理流量削峰数据同步最终一致性 消息队列模式 发布/订阅模式&#xff1a;一对多广播工作队列模式&#xff1a;竞争消费死信队列…

第26节 Node.js 事件

Node里很多对象会分发事件&#xff1a; 每次有连接的时候net.Server会分发事件&#xff0c;当文件打开的时候fs.readStream会分发事件。所有能分发事件的对象都是 events.EventEmitter的实例。通过require("events");能访问这个模块。 一般来说&#xff0c;事件名都…

LangChain + MCP + vLLM + Qwen3-32B 构建本地私有化智能体应用

一、私有化智能体应用 在本专栏的前面文章基于Spring AI MCP实现了本地 ChatBI 问答应用&#xff0c;本文还是依据该场景&#xff0c;采用 LangChain vLLM Qwen3-32B MCP 技术栈构建该流程&#xff0c;整体过程如下图所示&#xff1a; 实现效果如下所示&#xff1a; 关于 M…

AKS升级路线最佳实践方案

前言 Kubernetes 社区大约每 4 个月发布次要版本&#xff0c;次要版本包括新增功能和改进。补丁发布更为频繁&#xff08;有时每周都会发布&#xff09;&#xff0c;适用于次要版本中的关键 Bug 修复。修补程序版本包括针对安全漏洞或主要 bug 的修复。对于受支持版本列表以…

树莓派智能小车基本移动实验指导书

1.安装LOBOROBOT库函数 LOBOROBOT.py代码如下&#xff1a; #!/usr/bin/python # -*- coding: utf-8 -*-import time import math import smbus import RPi.GPIO as GPIODir [forward,backward, ]class PCA9685:# Registers/etc.__SUBADR1 0x02__SUBADR2 …

如何对目标检测算法RT-DETR进行创新和改进:突破瓶颈,提升性能!

更多精彩&#xff0c;详见文末~~~ 在目标检测的高速发展中&#xff0c;RT-DETR作为DETR&#xff08;DEtection TRansformer&#xff09;的高效变体&#xff0c;凭借其优异的性能和较快的推理速度&#xff0c;已经成为许多实际应用中的首选算法。然而&#xff0c;尽管RT-DETR在…

Java-String

前言 package com.kjxy.st;public class TestString1 {public static void main(String[] args) {String s1 "hello";String s2 "hello";String s3 new String("hello");String s4 new String("hello");System.out.println(s1 s2…