Java 手写 AI Agent:ZenoAgent 实战笔记

Java 手写 AI Agent:ZenoAgent 实战笔记
摘要:作为一个长期使用 Java 的后端开发者,我对 AI Agent 的内部运作机制充满了好奇。为了深入理解 Agent 的工作原理,我决定动手写一个简单的 Agent 系统 —— ZenoAgent。本文记录了我在这个过程中的学习心得与技术实践,包括如何手写 ReAct 循环在分布式环境下实现 Human-in-the-loop尝试复刻类 o1 的流式思考以及探索错误处理机制。希望这些踩坑经验能给同样想探索 AI 的 Java 开发者一些参考。

👀 在线体验:项目已部署上线,欢迎试玩:线上部署地址
(注:受限于服务器资源,线上本地部署了 Qwen3:8B 模型(参见另一篇博文华为云服务器本地部署大模型实战),虽不如商业模型聪明,但足以演示 Agent 的核心能力)

在这里插入图片描述

💡 写在前面:我的学习初衷

市面上已经有了像 LangChain 和 AutoGen 这样非常优秀的成熟框架,它们功能强大且生态丰富。但作为一名对 AI 技术充满热情的 Java 开发者,我总觉得光是调用 API 或使用现成的 SDK,很难真正触达 Agent 技术的核心。

我发起 ZenoAgent 这个项目的初衷非常简单:我想通过亲手造一遍轮子,来弄清楚 Agent 到底是怎么跑起来的。

在这个过程中,我主要关注以下几个学习点:

  1. 原理探索:不依赖复杂的封装,亲手实现 ReAct 循环,理解 LLM 是如何规划和执行任务的。
  2. 全栈打通:尝试从后端推理到前端可视化,完整走一遍 AI 应用的开发流程。
  3. 工程实践:看看在 Java 生态下,如何处理流式响应、并发控制等实际问题。

ZenoAgent 是我学习过程中的一份“大作业”,虽然它还很稚嫩,但这趟探索之旅让我收获颇丰。


🏗️ 核心架构概览

ZenoAgent 采用经典的 DDD(领域驱动设计) 分层架构,确保了代码的高内聚低耦合。

Data & External

Backend (Spring Boot)

Infrastructure

Core Engine

HTTP/SSE

Call

Retrieve

Load Context

User

API Layer (Controller)

Application Service

ReAct Engine

Agent State Machine

Thinking Engine

Action Executor

Observation Engine

MCP Tool Client

RAG Enhancer

Memory System

Redis
Short-term Memory
Dist. Lock

Mysql
long-term Memory

PostgreSQL
pgvector

LLM Provider
(OpenAI/DeepSeek)

External Tools

  • 后端:Java 17, Spring Boot 3, LangChain4j (LLM 交互核心), Redisson (分布式协调)
  • 数据存储:PostgreSQL + pgvector (向量数据库), Redis (缓存与消息队列)
  • 前端:Vue 3, TypeScript, Tailwind CSS, SSE (Server-Sent Events)

🔥 硬核亮点解析

1. 上下文构造与思维链控制 (Context Construction & CoT)

Agent 聪明的关键在于如何构建 Prompt。ZenoAgent 的 ThinkingEngine 采用了 Hybrid Context Assembly(混合上下文组装) 的策略。

🧩 混合上下文组装原理 (Hybrid Strategy)

我们不再简单地将所有信息拼接到一个巨大的 User Prompt 中,而是充分利用 LLM 的 Native Messages 结构,将上下文拆解为三个部分:

  1. System Message: 定义角色、工具使用规范和输出格式(JSON Schema)。
  2. History Messages (Native): 直接插入 List<ChatMessage>,保留 User/Assistant 的原生角色信息。这让模型能完美理解对话流转,且有利于 KV Cache 加速。
  3. Current Context (Text): 将“当前目标”、“可用工具”、“最近的 Action 执行结果”拼接到最新的一条 UserMessage 中。
// ThinkingEngine.java 核心重构List<ChatMessage> messages =newArrayList<>();// 1. System Prompt (规则与约束) messages.add(newSystemMessage(sysPrompt));// 2. Native History (利用 KV Cache) messages.addAll(context.getMessages());// 3. Current Context (动态任务背景)String currentStepPrompt =""" ## 当前目标 %s ## 可用工具 %s ## 最近执行结果 %s """.formatted(goal, tools, actionHistory); messages.add(newUserMessage(currentStepPrompt));

这种设计既保留了 Agent 执行过程的灵活性(通过 Text 描述复杂的 Action/Result),又利用了 LLM 原生对话结构的优势(语义清晰、Token 复用)。

🧠 如何控制大模型"先想后做"?

我们通过 Prompt Engineering 强制 LLM 遵循 Thought-Action-Observation 范式。

  • 强制输出 <thinking> 标签:在 System Prompt 中明确要求 LLM 先输出 XML 格式的思考过程,再输出 JSON 格式的动作指令。
  • 流式解析状态机:后端维护一个轻量级的状态机,实时解析流式响应。

"三段式"输出协议(踩坑经验)

⚠️ 踩坑记录:最初我只要求模型输出 <thinking><actions> 两部分。结果发现,许多模型(尤其是非顶流模型)在输出完 </thinking> 后,会习惯性地加一句“好的,根据以上分析,我现在的计划是…”,然后才输出 JSON。这会导致 JSON 解析器直接报错。

最终解决方案:引入一个强制的“截断标记” <THINKING_DONE>

1. <thinking>...</thinking> 2. <THINKING_DONE> <-- 强制刹车信号 3. <actions>{...}</actions> 

后端检测到 <THINKING_DONE> 后,会触发状态流转,并忽略该标记之前的所有非 JSON 文本,确保 <actions> 的纯净。

Yes

Stream Delta

Yes

Extract JSON

Start

Detect

State: THINKING

Frontend UI

Detect

State: PARSING_JSON

Action Executor

这种机制不仅让 Agent 的决策更理性(Chain of Thought),还让用户能实时看到 AI 的"心路历程",体验感拉满。

🛡️ 防御式编程:四重保障解决“大模型不听话”

为了让 LLM 稳定输出符合 Java 强类型要求的 JSON,我采取了一套四重保障机制

    • 剔除 ```json 和 ` ````包裹。
    • 自动补全截断的 JSON:利用堆栈检测括号平衡,自动补齐末尾缺失的 }

自愈式重试循环 (Self-Correction Loop)这是最关键的一道防线。当解析失败时,不要直接抛出异常,而是将错误堆栈作为 Prompt 反馈给模型。

try{ finalActions =parseThinkingResult(fullText);}catch(LLMParseException e){// 将错误信息回传给模型,进行"反思修正" retryHint ="上次输出解析失败:"+ e.getMessage()+",请严格修正格式。";// 进入下一轮循环 retry...}

实测表明,这种机制能将复杂任务的执行成功率从 60% 提升至 95% 以上。

鲁棒性解析 (Robust Parsing)后端解析器不能有“洁癖”。我的 cleanJsonResponse 方法会自动清洗常见的 LLM 格式错误:

// PromptGuidedThinkingEngine.javaif(depth >0&& start >=0){ log.warn("检测到 JSON 截断,尝试自动补全...");// 自动补齐剩余的 '}'}

原生 Response Format (Native API)对于支持 JSON Mode 的模型,在调用时直接注入约束:

// 强制模型以 JSON Object 模式输出ResponseFormat.builder().type(ResponseFormatType.JSON).build();

Prompt 强约束 (Prompt Engineering)DECISION_FRAMEWORK_PROMPT 中,不仅定义 Schema,更要预判错误。例如,明确禁止 Markdown 代码块:

privatestaticfinalString JSON_OUTPUT_FORMAT_PROMPT =""" ## 输出格式强制约束(必须100%遵守,任何违规都会导致输出无效) 1. 输出内容必须是**纯合法JSON对象**,无任何非JSON文本(如<thinking>标签、注释、说明文字等); 2. JSON对象包含2个必填顶级字段: - thinking:字符串类型,填写你的逻辑推演过程(需清晰说明“为什么选择该动作类型”“参数如何确定”等); - actions:数组类型,仅包含1个动作指令对象(单轮仅输出1个动作); 3. actions数组中的每个动作对象必须包含以下基础字段: - actionType:字符串类型,仅允许取值【TOOL_CALL/RAG_RETRIEVE/LLM_GENERATE/DIRECT_RESPONSE】,严禁使用其他值; - actionName:字符串类型,填写动作名称(如search_weather/retrieve_knowledge/generate_content/reply_user); - reasoning:字符串类型,填写选择该动作的简短理由(区别于thinking的详细推演); 4. 不同actionType需额外包含对应必填参数字段(缺失会判定为无效): ..... """;

2. ReAct 模式设计与工程实现

ZenoAgent 的核心并未采用现成的 Agent 框架,而是手写了一个基于状态机的 ReAct 循环引擎。在设计过程中,我重点思考了Action 的原子化交互的低延迟

2.1 Action 的抽象设计

为了让 LLM 的输出更可控,我将 Agent 的所有能力抽象为 4 种原子 Action:

  • TOOL_CALL:调用外部工具(通过 MCP 协议)。
  • RAG_RETRIEVE:检索知识库(弥补模型长短期记忆)。
  • LLM_GENERATE:让子模型处理纯文本生成任务(如润色、翻译)。
  • DIRECT_RESPONSE:直接向用户输出结果。

这种设计让 ReAct 循环的边界非常清晰:Thinking -> Action List -> Execution -> Observation

2.2 为什么设计 DIRECT_RESPONSE?(性能优化的关键)

在早期的 ReAct 实践中,我发现一个严重的性能浪费:
对于简单的闲聊(如“你好”)或知识性问答,标准 ReAct 模式太重了。

  • 传统模式:用户问“你好” -> LLM 思考 -> 决定不调用工具 -> 输出 Final Answer。这通常需要消耗完整的推理步骤,甚至有的框架会强制要求先调用一个 talk 工具,增加了一次毫无意义的 RTT(网络往返)。
  • ZenoAgent 模式:我将“回复用户”设计为一种 Action (DIRECT_RESPONSE)。

优化收益
当模型判断无需使用工具时,它可以直接在决策阶段生成 DIRECT_RESPONSE 动作。这意味着“思考”和“回复”在一次 LLM 调用中同时完成了,对于简单场景,延迟降低了 50% 以上,且大幅节省了 Token。

2.3 并发执行机制 (Parallel Execution)

传统的 ReAct 模式是严格串行的(Think -> Act -> Observe -> Think),这会导致严重的性能瓶颈。

ZenoAgent 对此进行了改良:允许 LLM 在一次 Thinking 过程中输出多个 Action
例如,当用户问“对比北京和上海的天气”时,Agent 会同时生成两个查询动作,后端利用 Java 的 CompletableFuture 并行分发请求。

// ActionExecutor.java 核心逻辑publicList<ActionResult>executeParallel(List<AgentAction> actions){List<CompletableFuture<ActionResult>> futures = actions.stream().map(action ->CompletableFuture.supplyAsync(()->execute(action), executor)).toList();// 并行等待所有结果return futures.stream().map(CompletableFuture::join).toList();}

这种设计将多步串行任务的耗时压缩到了最慢的那一个 Action 的耗时,响应速度提升显著。

2.4 实战踩坑:isComplete 的语义陷阱

在实现 DIRECT_RESPONSE 时,我引入了一个 isComplete 字段来控制循环是否结束。看似简单,却踩了一个大坑。

问题复现
当用户说“查天气”,Agent 需要反问“请问是哪个城市?”。
此时,Agent 认为任务并未完成,于是将 isComplete 设为 false
结果:后端 ReAct 引擎收到 false 后,认为 Agent 还要继续干活,于是不等待用户回复,直接进入下一轮循环。Agent 发现没拿到城市名,又问一遍,导致死循环。

深度反思
大模型对 Complete(完成)的理解是“任务终结”。但在交互式场景中,我们需要的是“控制权移交”。

解决方案
我重构了 Prompt 中对 isComplete 的语义定义,从“任务状态”转变为“流控制状态”:

  • isComplete=true移交控制权
    • 场景 1:任务彻底完成。
    • 场景 2:需要提问并等待用户回复(虽然任务没完,但我得停下来等你)。
  • isComplete=false保持控制权
    • 场景:发送“正在查询”、“已调用工具”等过程通知,不等待用户回复,流程自动继续。

修正后的 Prompt 片段

// PromptGuidedThinkingEngine.java3. 需要向用户输出内容时使用 DIRECT_RESPONSE,并通过 isComplete 字段控制流程: - isComplete=true:**终止本轮**。用于:1. 任务彻底完成;2.**需要提问并等待用户回复**。此时意味着 Agent 暂停工作移交控制权给用户; - isComplete=false:**过程通知**。用于:任务仍在进行中,仅向用户发送进度提示,**不等待用户回复**,流程自动继续。 

通过这种语义重构,配合 Few-Shot 示例(明确展示“反问用户”时设为 true),Agent 终于学会了在适当的时候“闭嘴等待”。


3. 分布式环境下的 “Human-in-the-loop”

在企业级应用中,敏感操作(如删除文件、转账)必须经过人工确认。但在 ** 集群部署**场景下,这面临一个巨大挑战:

问题:Agent 的推理线程(持有 SSE 连接)可能在 Pod A 上运行,而用户点击“确认”的 HTTP 请求可能打到了 Pod B。如何让 Pod A 知道用户在 Pod B 做了确认?

解决方案:基于 Redis 阻塞队列的分布式信号量

ZenoAgent 利用 Redisson 实现了跨实例的线程挂起与唤醒。

分布式确认时序图

Backend (Pod B)RedisBackend (Pod A)FrontendUserBackend (Pod B)RedisBackend (Pod A)FrontendUserAgent runs thinking loop...Thread BLOCKED on Redis QueueReceived by different Pod!Thread ResumesDecide to call 'delete_file'Create BlockingQueue (key=exec_123)SSE: confirmation_request (id=exec_123)Click "Approve"POST /confirm (id=exec_123, action=APPROVE)Queue.push("APPROVED")Unblock: "APPROVED"Execute ToolSSE: tool_result

代码实现参考:

// ToolConfirmationManager.javapublicToolExecutionDecisionwaitForDecision(String executionId){RBlockingQueue<ToolExecutionDecision> queue = redissonClient.getBlockingQueue(KEY_PREFIX + executionId);// 阻塞等待用户决策,支持超时自动拒绝return queue.poll(60,TimeUnit.SECONDS);}

4. 流式思考:从 Prompt 模拟到原生 API 适配

为了让用户感知到 AI 的“思考过程”,ZenoAgent 实现了基于 Prompt 注入和原生支持的思考引擎,支持根据配置自动注入,分别适配普通模型和推理模型。

方案一:Prompt 引导的模拟思考 (通用方案)

对于普通模型(如 GPT-4, Qwen-2.5),我们通过 System Prompt 强制要求模型在输出最终 JSON 动作前,先在 thinking 字段中输出思维链。

核心原理
将“思考”作为 JSON 结构的一部分。虽然这违反了“纯数据”的原则,但能确保模型在生成 Action 之前必须先进行逻辑推演(CoT)。

实现挑战
在流式响应中,JSON 是逐字生成的。我们需要在后端实时解析这个不完整的 JSON 字符串,提取出 thinking 字段的增量内容推送到前端。

代码参考
PromptGuidedThinkingEngine.java 使用了状态机来解析流式 JSON:

// 伪代码:从流式 JSON 中提取 thinking 字段if(token.contains("\"thinking\"")){ isThinking =true;}if(isThinking &&!token.contains("\"actions\"")){ sseEmitter.send(token);// 实时推送思考过程}
方案二:原生 Reasoning 字段适配 (未来趋势)

随着 DeepSeek-R1 和 OpenAI o1 的发布,模型开始原生支持“推理”。这种模式下,思考过程不再占用 content 字段,而是通过独立的 reasoning 通道传输。

核心原理
模型在输出 Response 之前,会先在一个独立的 Channel 中输出思维链(CoT)。这个过程对 Prompt 是透明的,属于模型原本的能力。客户端需要监听特定的 API 字段(如 reasoning_content)来获取这部分数据。

代码参考
OpenAIReasoningThinkingEngine.java 实现了对原生推理字段的监听。

实战遇到的协议不匹配问题
除了模拟思考,我也尝试接入模型原生的思考能力(如 DeepSeek-R1),但在对接 Ollama 时遇到了协议标准不统一的深坑。

问题复现
我在 OpenAIReasoningThinkingEngine.java 中尝试解析流式响应,发现虽然模型在“思考”,但后端却拿不到内容。

排查过程

  1. 社区现状:查阅 Ollama Issue #13372 发现,由于 OpenAI 官方 Spec 尚未标准化该字段,各家实现出现了分歧。

源码比对:LangChain4j 框架底层期望的字段却是 reasoning_content

// LangChain4j Delta.java@JsonPropertyprivatefinalString reasoningContent;// 字段名不匹配!

抓包分析:Ollama 的 API 返回字段是 reasoning

{"choices":[{"delta":{"role":"assistant","reasoning":"好的,用户想要..."// Ollama 使用 reasoning}}]}

后果与反思
这意味着,如果我们直接用 LangChain4j 调用 Ollama 部署的 R1 模型,模型在耗费算力进行“昂贵”的思考(首 Token 延迟高),但客户端却直接丢弃了这部分数据。这不仅是体验缺失,更是算力的无端浪费。目前的解决方案只能是通过自定义 StreamingResponseHandler 手动适配字段。

🚧 实战踩坑:流式标签泄露与 Lookahead Buffer

在实现方案一(Prompt 模拟)时,我遇到了一个非常棘手的问题:前端偶尔会闪现出 </thinking 这样残缺的标签。

问题根源:LLM 的 Token 是逐字吐出的。当模型输出 </thinking> 时,可能先吐出 </,此时后端检测不到完整标签,误以为是普通文本就直接发给了前端;紧接着模型吐出 thinking>,后端这才反应过来是标签,拦截了后半段,但前半段 </ 已经泄露了。

解决方案:引入 Lookahead Buffer(前缀缓冲) 机制。

我们在后端增加了一个判断逻辑:在流未结束时,如果缓冲区末尾匹配 </thinking> 的任意前缀(如 <</</t),先扣留这部分内容不发。只有当下一个 Token 进来,证明它拼不成完整标签时,才把缓冲区的内容释放出去。

// ThinkingEngine.javaif(thinkingEndIdx ==-1&&!isComplete){// 检查末尾是否是 </thinking> 的一部分int bufferLen =calculatePartialTagMatchLength(currentContent);if(bufferLen >0){// 扣留这部分内容,暂不发送 logicalEnd -= bufferLen;}}
进阶优化:事件驱动的国际化 (i18n)

为了解决这个问题,我重构了事件系统,不再发送自然语言,而是发送结构化状态事件(如 agent:status:thinking_process)。前端接收到事件 ID 后,再根据当前用户的语言设置(zh-CN/en-US)渲染对应的文案。这不仅解耦了前后端,也让国际化变得异常简单。


5. RAG 知识库:从摄入到召回的全链路优化

ZenoAgent 的 RAG 模块(RAGEnhancer)不仅仅是简单的向量相似度搜索,而是构建了一套完整的知识流转体系。

📂 文档摄入流程 (Document Ingestion)

我们使用 Apache Tika 进行全格式解析,并配合 Recursive Character Splitter 进行语义分块。

Ingestion Pipeline

Tika Parser

Recursive Splitter

MetaData Injection

Embedding Model

pgvector

User Upload

Document Service

Raw Text

Text Segments

Segments + Meta

Vectors

PostgreSQL

🔍 动态召回策略 (Dynamic Retrieval)
  • 元数据隔离:基于 knowledgeId 进行物理/逻辑隔离,确保多用户数据安全。
  • 动态表名路由:根据 Embedding 模型的维度(如 768 vs 1536),动态路由到不同的向量表,支持多模型共存。
  • 结构化上下文注入
// RAGEnhancer.java 核心逻辑 embeddingStore.search(EmbeddingSearchRequest.builder().queryEmbedding(queryVector).filter(metadataKey("knowledgeId").isEqualTo(currentKnowledgeId))// 关键:租户隔离.maxResults(5).minScore(0.75)// 过滤低质量结果.build());

7. 自愈式错误处理 (Self-Correction)

ZenoAgent 不仅能执行工具,还能从错误中学习。当工具调用失败(如参数错误、网络超时)时,Agent 不会直接崩溃,而是进入自愈循环

  • 错误捕获ActionExecutor 捕获异常,生成包含错误信息的 ActionResult
  • 上下文反馈:错误信息被记录到 AgentContext 中。
  • Prompt 注入:在下一轮 Thinking 阶段,Prompt 会包含:“上一步操作失败,错误信息:Invalid argument…”。
  • 智能修正:LLM 看到错误后,会尝试修正参数或选择替代工具,重新发起调用。
这种机制让 ZenoAgent 具备了极强的鲁棒性,能够自动应对 API 变更或临时性网络故障。

8. 动态工具发现与热加载

基于 MCP (Model Context Protocol),ZenoAgent 实现了工具的动态管理。

  • 热加载 (Hot Reload)McpConfigLoader 监听配置文件变更。一旦修改 mcp.json,系统会自动重新连接 MCP Server 并刷新工具列表,无需重启服务。
  • 分组管理:支持对工具进行分组(Group),通过配置即可灵活启用/禁用特定业务线的工具集。

📸 系统预览

为了让大家更直观地感受 ZenoAgent,这里放几张系统截图:
欢迎页:

在这里插入图片描述


智能对话

在这里插入图片描述

Agent 配置 (MCP)

在这里插入图片描述


知识库

在这里插入图片描述

🚀 总结与展望

ZenoAgent 是我边学习边实践的产物。通过 Spring Boot 的工程化能力,我们可以构建出比 Python 原型更稳定、更易于扩展的 Agent 系统。

目前项目已开源,欢迎感兴趣的朋友 Star ⭐️ 和 PR!

让我们一起探索 AI Agent的无限可能!

Read more

部署OpenClaw首选远程软件——UU远程:从准备到落地,新手也能轻松上手

部署OpenClaw首选远程软件——UU远程:从准备到落地,新手也能轻松上手

前言 在企业为客户远程部署、技术博主带粉丝实操教学、远程技术支持等真实场景中,稳定、低延迟、高同步的远程工具是完成 AI 工具部署的关键。本地部署无需依赖云服务器,成本更低、更安全,但传统远程软件往往延迟高、操作卡顿,严重影响部署效率与体验。 本文将以OpenClaw轻量 AI 辅助服务工具为部署对象,全程依托网易 UU 远程实现流畅远程控制与协助,详细讲解网易 UU 远程的核心优势,从 UU 远程环境准备、OpenClaw 远程部署,到基于网易UU远程的实时监视 OpenClaw 状态,零门槛、无复杂配置。借助网易 UU 远程的低延迟与高稳定性,企业可高效为客户远程交付,博主可轻松带粉丝同步实操,新手也能跟着完整落地。 本篇文章分别从准备工作、远程部署、远程监视三个维度进行实操教学,一步步拆解如何运用远程UU进行远程部署openclaw。 一、网易UU远程介绍 网易UU远程是网易出品的一款轻量化、零配置、高稳定的远程控制工具,区别于传统远程工具(

By Ne0inhk
《C/C+++ Boost 轻量级搜索引擎实战:架构流程、技术栈与工程落地指南——构造正/倒排索引(中篇)》

《C/C+++ Boost 轻量级搜索引擎实战:架构流程、技术栈与工程落地指南——构造正/倒排索引(中篇)》

前引:这是一个聚焦基础搜索引擎核心工作流的实操项目,基于 C/C++ 技术生态落地:从全网爬虫抓取网页资源,到服务器端完成 “去标签 - 数据清洗 - 索引构建” 的预处理,再通过 HTTP 服务接收客户端请求、检索索引并拼接结果页返回 —— 完整覆盖了轻量级搜索引擎的端到端逻辑。项目采用 C++11、STL、Boost 等核心技术栈,搭配 CentOS 7 云服务器 + GCC 编译环境(或 VS 系列开发工具)部署,既适配后端工程的性能需求,也能通过可选的前端技术(HTML5/JS 等)优化用户交互,是理解搜索引擎底层原理与 C++ 工程实践的典型案例 目录 【一】Jieba分词工具 【二】正/倒排索引结构设计

By Ne0inhk

前端 | 一篇搞懂CSS盒模型核心:padding、margin、border与box-sizing、border-radius

一篇搞懂CSS盒模型核心:padding、margin、border与box-sizing、border-radius 写CSS布局时,很多人会被“盒子”里的padding、margin、border绕晕——明明设置了相同的宽度,加个padding就变形;调整margin又莫名影响相邻元素;border的宽度到底算不算总宽度?其实只要把“盒子”的逻辑拆透,这些问题都能迎刃而解。今天就用最直白的方式,把这些核心概念讲清楚,让你看完就能直接用。 先分清三个“间隔”:padding、margin、border的核心区别 CSS里的每个元素,都像一个“快递盒子”——内容是盒子里的物品,而padding、margin、border,就是盒子本身和周围的不同间隔。三者的核心区别,本质是“位置不同、作用对象不同、是否占盒模型空间不同”,用一句话总结:border是盒子的“壳”,padding是壳和内容的“内填充”,margin是盒子和其他盒子的“外距离”。 1. border:盒子的“

By Ne0inhk
基于YOLO26/11/v8算法的Web目标检测系统,人脸表情识别系统,Django+Vue3 的前后端分离,实现摄像头实时识别,YOLO26/YOLO11/v8 + LLM大模型智能分析,科研必备

基于YOLO26/11/v8算法的Web目标检测系统,人脸表情识别系统,Django+Vue3 的前后端分离,实现摄像头实时识别,YOLO26/YOLO11/v8 + LLM大模型智能分析,科研必备

✨ 更新日志 * ✔️ 2026/3/3,2.0 版本,前端导航栏改为侧边栏系统,视频流采用websocket框架延迟更低, YOLO26/YOLO11/YOLOv8 视频流更稳定,在之前的系统增加 LLM 大模型智能分析,是科研必备,支持 YOLO26/11/v8 分类模型、目标检测、分割、obb、关键点检测任务,还支持双模型联合检测与识别,如人脸表情识别、人脸识别等一些识别任务需要检测模型与分类模型共同完成,在人脸表情识别中,单独使用检测模型去识别人脸表情也不是不可以,但有一个问题数据集如果全是头部照片的话,当模型预测的照片是全身照片时,模型识别准确率就没有这么高了, 那么这时候可以用检测模型识别人脸,把人脸信息输入到表情分类模型进行分类即可,反正这是一个通用的系统,更换自己模型即可,大家懂得都懂的,更多功能看下文即可。 摘要 在人工智能迈向通用化(AGI)的今天,“视觉感知 + 语言理解”的多模态联合是未来的趋势。单纯的检测画框已经无法满足复杂的业务需求,如何让系统“看懂”

By Ne0inhk