【Agent开发第十五期】部署上线:把 Alex 搬进浏览器,15 期全栈串联收官

文章目录

    • 一、前言:发动机造好了,但它还住在黑窗口里
    • 二、概念对齐:三件事把"能跑"变成"能上线"
      • [2.1 Web 外壳:给命令行套一层 HTTP 服务](#2.1 Web 外壳:给命令行套一层 HTTP 服务)
      • [2.2 SSE 流式:把"打印报告"变成"打字机"](#2.2 SSE 流式:把"打印报告"变成"打字机")
      • [2.3 Docker:把"我机器上能跑"变成"处处可跑"](#2.3 Docker:把"我机器上能跑"变成"处处可跑")
      • [2.4 一个关键澄清:`_ensure_session` 才是这期真正的隐藏主角](#2.4 一个关键澄清:_ensure_session 才是这期真正的隐藏主角)
    • [三、动手做:给 Alex 装一层 Web 外壳](#三、动手做:给 Alex 装一层 Web 外壳)
      • [3.1 双入口:`main.py` 分发](#3.1 双入口:__main__.py 分发)
      • [3.2 `llm_call_stream`:流式调用](#3.2 llm_call_stream:流式调用)
      • [3.3 `stream_chat_with_tools`:流式工具循环](#3.3 stream_chat_with_tools:流式工具循环)
      • [3.4 `web.py`:FastAPI 六端点 + 内嵌前端](#3.4 web.py:FastAPI 六端点 + 内嵌前端)
      • [3.5 会话列表 + 续聊:事件溯源的直接产品](#3.5 会话列表 + 续聊:事件溯源的直接产品)
      • [3.6 `GOAL_STATE` 跨请求:无状态 HTTP + 有状态 Agent](#3.6 GOAL_STATE 跨请求:无状态 HTTP + 有状态 Agent)
      • [3.7 Docker 三件套](#3.7 Docker 三件套)
    • [四、跑起来:亲眼看 Alex 变成"打字机服务"](#四、跑起来:亲眼看 Alex 变成"打字机服务")
      • [现象 1:打字机流式](#现象 1:打字机流式)
      • [现象 2:工具实时播报](#现象 2:工具实时播报)
      • [现象 3:会话列表 + 续聊](#现象 3:会话列表 + 续聊)
      • [现象 4:目标跨请求存活](#现象 4:目标跨请求存活)
      • [现象 5:Docker 交付](#现象 5:Docker 交付)
    • 五、执行脚本(本期新增完整代码)
    • 六、总结:十五期,把一台发动机装进了车

一、前言:发动机造好了,但它还住在黑窗口里

第 15 期,也是最后一期。

先回头看这一路。第 01 期,Alex 是个只会调 API 的"婴儿";到第 14 期,它已经攒齐了记忆、人设、技能库、工具、TodoList、子代理、团队、MCP、Hooks 安检、多模型路由、目标驱动 ------ 28 个工具,一头完整的 Agent。

但有个尴尬的事实我一直在回避:它一直住在命令行里。 每次用,都要开一个终端,敲 python -m ...,目标:... 或者一句句和它对答,盯着黑窗口。

这是台"发动机",不是台"能上路、能交付的车"。搞到第十五期才说破,有三件必须补的事:

  1. 命令行不是产品 。没人会天天开终端和一个黑窗口聊天,真正的形态是在浏览器里打开一个舒服的界面。
  2. 一次性整段吐回答,像在"打印报告" 。前 14 期所有调用都是 stream=False:等 LLM 把整段话算完,一次性 print 出来。用户盯着黑屏干等几秒,中间一点反馈都没有。ChatGPT 那种"一个字一个字蹦出来"的打字机,才是产品的标配。
  3. "我机器上能跑" ≠ "能交付"。依赖怎么装、路径怎么放、API Key 怎么进、记忆怎么持久化 ------ 把 Alex 交给别人,不能指望对方照着 README 手搓一遍。

一句话:前 14 期造的是"发动机",第 15 期要把它装进一辆能上路、能交付的车。

本文是系列第 15 期(最终期),做三件事,而且一行不改前 14 期的 Agent 能力,只加外壳和交付:

  • Web UI :用 FastAPI 把 Alex 包成服务,内嵌一段单文件前端,浏览器打开 http://localhost:8000 即用
  • 流式响应(SSE):让它的每个字"边生成边到前端",工具调用也实时播报,把"打印报告"变成"打字机"
  • Docker 部署:Dockerfile + docker-compose,一次构建、处处可跑

老规矩,本文结构:概念对齐 → 动手写代码 → 跑起来看效果 → 完整代码 → 总结。系列累积式迭代,最后一棒:step15 = step14 + 部署上线。

二、概念对齐:三件事把"能跑"变成"能上线"

2.1 Web 外壳:给命令行套一层 HTTP 服务

把前 14 期的纯逻辑(Python 函数)套一层 FastAPI,拆成几个 HTTP 端点。核心就是这条链:

复制代码
浏览器输入 → fetch POST /chat → StreamingResponse → 逐块推回浏览器

FastAPI 只是"壳",内核还是那套 build_system_prompt() + 工具循环 + 记忆层。壳与核的边界,是这一期最重要的手感:换壳(Web/CLI/未来接 API 网关)不动核。

2.2 SSE 流式:把"打印报告"变成"打字机"

前 14 期,LLM 的答案是一次性返回的。要打字机体验,有两个选择:

方案 连接 方向 适用场景 复杂度
SSE 走普通 HTTP 服务端 → 客户端单向下推 聊天:发一条,持续吐多个事件直到结束 低,fetch 流式读即可
WebSocket 独立长连接协议 双向 协同编辑、实时对战、双向推送 高,要维护长连接生命周期

聊天是典型的"单向流 + 每轮结束 ":客户端发一条消息,服务端持续吐 delta / tool_call / tool_result 多个事件,直到 done。SSE 只需要 HTTP,浏览器原生 fetch + getReader() 就能读 ------ 对"AI 打字机"这个需求,SSE 是最小够用方案,没必要上 WebSocket。

2.3 Docker:把"我机器上能跑"变成"处处可跑"

依赖装不对、路径放错、环境变量漏传 ------ 交付路上的脏活,交给 Docker 三件套:

  • Dockerfile:分层缓存,依赖最稳放最前,改代码只重建最后一层
  • docker-compose.yml :一键起服务,env_file 注入 API Key,volumes 挂载 memory/ 让记忆持久化
  • .dockerignore :挡住 .env、__pycache__、.venv、memory/ 这些不该进镜像的东西

2.4 一个关键澄清:_ensure_session 才是这期真正的隐藏主角

Web 服务是无状态的 HTTP,而 Alex 是"有状态的 Agent"------ 它记着上下文、记着目标、记着账单。这两者怎么衔接?这期最容易被忽略的坑就是:FastAPI 进程内的会话表,服务一重启就没了。 解法是 _ensure_session():首次(或重启后)从 memory/raw_history.jsonl 把该会话的 user/assistant 回放进上下文,于是刷新页面、甚至重启服务,点了那条会话还能接着聊。这件事留在第三节细说。

三、动手做:给 Alex 装一层 Web 外壳

进入代码。所有代码在 code/step15_deploy/ 这个模块化包 里 ------ 相比前 14 期的单文件脚本,这一期第一次拆成了包,但内核逻辑一行没动,step15 = step14 + 部署上线。

3.1 双入口:__main__.py 分发

python 复制代码
def main():
    if "--cli" in sys.argv:
        from .main import main as cli_main   # 回命令行(第 14 期入口)
        cli_main()
    else:
        from .web import run_web             # 默认起 Web(本期核心)
        run_web()

python -m step15_deploy 默认 Web,加 --cli 回命令行 ------ 两个入口共用同一套 build_system_prompt()、同一套 stream_chat_with_tools()、同一套工具和记忆层。

3.2 llm_call_stream:流式调用

路由层(第 13 期)新增一个流式版调用,和 llm_call 唯一的区别就是 stream=True:

python 复制代码
# model_router.py:流式版 llm_call,其余(模型选择、成本记账、重试)完全复用
def llm_call_stream(messages, task="main", tools=None):
    kwargs = _build_kwargs(messages, tools)
    kwargs["stream"] = True
    kwargs["stream_options"] = {"include_usage": True}   # 流式也要拿到 token 用量
    ...
    for delta in response:
        yield delta, model_id

3.3 stream_chat_with_tools:流式工具循环

这是本次最容易翻车的地方。OpenAI 流式返回里,工具调用的参数是分片到达的 :一个 tool_call 的 id、name、arguments 会拆成多个 chunk 分开送过来。stream.py 用 setdefault(tc.index, {...}) 按 index 累积分片,攒齐再执行:

python 复制代码
def stream_chat_with_tools(messages, session_id=None):
    while True:
        content_parts, tool_calls = [], {}
        for delta, model_id in llm_call_stream(messages, task="main", tools=TOOLS):
            if delta.content:
                content_parts.append(delta.content)
                yield {"event": "delta", "text": delta.content, "model": model_id}
            if delta.tool_calls:
                for tc in delta.tool_calls:
                    slot = tool_calls.setdefault(tc.index, {"id": "", "name": "", "arguments": ""})
                    if tc.id: slot["id"] = tc.id
                    if tc.function and tc.function.name: slot["name"] += tc.function.name
                    if tc.function and tc.function.arguments: slot["arguments"] += tc.function.arguments
        content = "".join(content_parts).strip()
        if tool_calls:
            # 落盘 assistant.tool_calls,逐个执行工具,回灌,再循环
            ...
            for slot in ordered:
                yield {"event": "tool_call",  "name": slot["name"], "arguments": slot["arguments"]}
                fn_args, result = _run_one_tool(slot["id"], slot["name"], slot["arguments"])
                yield {"event": "tool_result", "name": slot["name"], "result": result}
                ...
            continue
        # 无工具 → 落盘回答 → done
        append_and_persist(messages, {"role": "assistant", "content": content}, session_id)
        yield {"event": "done", "text": content}
        return

事件协议就五种(加一个 [DONE] 结束哨兵):

复制代码
{"event": "delta",       "text": "...", "model": "deepseek-chat"}
{"event": "tool_call",   "name": "...", "arguments": {...}}
{"event": "tool_result", "name": "...", "result": "..."}
{"event": "done",        "text": "..."}
{"event": "error",       "message": "..."}

3.4 web.py:FastAPI 六端点 + 内嵌前端

python 复制代码
app = FastAPI(title="Alex · 第 15 期 Web UI", docs_url=None, redoc_url=None)

@app.get("/health")            # 健康检查
@app.get("/goal")              # 读当前目标契约(第 14 期伏笔的 Web 版验证点)
@app.get("/sessions")          # 会话列表(左侧栏)
@app.get("/history/{session_id}")   # 回放某会话并重建上下文
@app.get("/")                 # 内嵌前端(单文件 HTML)
@app.post("/chat")            # 对话核心,返回 SSE

/chat 是心脏,它把 stream_chat_with_tools 的每个事件序列化成 SSE 的 data: 行:

python 复制代码
@app.post("/chat")
async def chat(payload: dict):
    message = (payload.get("message") or "").strip()
    session_id = payload.get("session_id") or "web-default"
    ...
    async def event_stream():
        messages = _get_messages(session_id)
        append_and_persist(messages, {"role": "user", "content": message}, session_id)
        messages[:] = trim_history(messages)                 # 截断
        if _ROUNDS[session_id] > COMPACT_THRESHOLD_ROUNDS:
            messages[:] = compact_history(messages)          # 压缩(复用记忆层)
        try:
            for ev in stream_chat_with_tools(messages, session_id):
                yield f"data: {json.dumps(ev, ensure_ascii=False)}\n\n"
        except Exception as e:
            yield f"data: {json.dumps({'event': 'error', 'message': str(e)}, ensure_ascii=False)}\n\n"
        yield "data: [DONE]\n\n"
    return StreamingResponse(event_stream(), media_type="text/event-stream",
                             headers={"Cache-Control": "no-cache",
                                      "X-Accel-Buffering": "no"})   # 关缓冲,让 SSE 逐字到前端

前端呢?没有引入任何框架 ------ 是一段内嵌在 web.py 里的 HTML (INDEX_HTML),深色聊天界面,fetch('/chat') 拿到 res.body.getReader() + TextDecoder 逐块读流,handleEvent 按事件类型渲染文字/工具气泡。

3.5 会话列表 + 续聊:事件溯源的直接产品

前 07 期开始,Alex 每轮消息就"产生即落盘"到 memory/raw_history.jsonl(append-only,每条带 session 字段)。Web 层没有另建一张"会话表" ------ 直接扫这个文件、按 session 聚合就是会话列表,记忆层和 Web 层共用同一份真相。

python 复制代码
def _list_sessions() -> list[dict]:
    """扫 raw_history.jsonl,按 session 聚合出会话列表(事件溯源,天然可回放)。"""
    ...
def _session_history(session_id) -> list[dict]:
    """读某会话的 user/assistant 消息,用于前端回放历史。"""
    ...

真正关键的兜底是 _ensure_session:

python 复制代码
def _ensure_session(session_id):
    if session_id in _SESSIONS:                       # 进程内已建过,直接复用
        return _SESSIONS[session_id]
    messages = [{"role": "system", "content": build_system_prompt()}]
    for it in _session_history(session_id):           # 重启后从落盘历史回放
        messages.append({"role": it["role"], "content": it["content"]})
    _SESSIONS[session_id] = messages
    return messages

这一步解决了 Web 版最容易被忽略的坑:进程内 _SESSIONS 在服务重启后就没了,但 Alex 不能每次重启都失忆。 _ensure_session 从 raw_history.jsonl 把该会话的 user/assistant 回放进 messages,于是"刷新页面 / 重启服务后再点那条会话"依然能接着聊。生产版把这份进程内状态换成 Redis/数据库,衔接方式完全不变。

3.6 GOAL_STATE 跨请求:无状态 HTTP + 有状态 Agent

第 14 期埋的伏笔:"目标驱动的 Agent 挂进服务里,用户发一个模糊目标就下线了,控制循环怎么跨 HTTP 请求继续跑?"

答案是:目标契约 GOAL_STATE 是模块级全局变量,天然跨 HTTP 请求存活。 用户在 Web 里让 Alex 订了目标,刷网页、再发一个请求,GET /goal 读到的还是同一个契约。web.py 用 from . import goal 直接读这个全局状态 ------ 这就是"无状态 HTTP"承载"有状态 Agent"的衔接点。

3.7 Docker 三件套

dockerfile 复制代码
# Dockerfile:分层缓存,依赖最稳放最前,改代码只重建最后一层
FROM python:3.11-slim
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY code/step15_deploy/ /app/code/step15_deploy/
COPY templates/ /app/templates/
COPY skills/ /app/skills/
COPY mcp_servers/ /app/mcp_servers/
WORKDIR /app/code
CMD ["python", "-m", "step15_deploy"]
yaml 复制代码
# docker-compose.yml:一键起服务,env 注入 API Key,memory 挂卷持久化
services:
  alex:
    build: .
    ports: ["8000:8000"]
    env_file: [".env"]
    volumes: ["./memory:/app/memory"]

四、跑起来:亲眼看 Alex 变成"打字机服务"

先在项目根目录配好 key,把服务起起来:

bash 复制代码
cd code
python -m step15_deploy          # 默认:Web 服务
# 浏览器打开 http://localhost:8000

现象 1:打字机流式

在浏览器里抛一个需要写一段代码或长回答的问题,观察文字是一个字一个字冒出来 ,而不是整块弹出。这是 delta 事件在起作用。

现象 2:工具实时播报

问一个会触发工具的问题(比如让它 查现在几点 或 set_goal 订目标),界面里「🔧 调用工具... ✅ 完成」的气泡和文字流交织出现 ------ 这就是 stream_chat_with_tools 把工具状态也当事件 yield 的效果。

现象 3:会话列表 + 续聊

左边「🗂 会话」栏列出所有历史会话(读 raw_history.jsonl 聚合)。随便点一条,该会话的 user/assistant 记录回放出来;然后刷新页面、甚至重启服务 ,再点那条,上下文从落盘历史重建,可以接着聊。/sessions + /history/{session_id} 把"事件溯源"变成了前端可见的会话列表。

现象 4:目标跨请求存活

在 Web 里用 目标: 前缀订一个目标,然后刷新页面,再点一下「🎯 目标」按钮 ------ 契约还在。这就证明了 GOAL_STATE 模块级全局变量跨 HTTP 请求幸存,第 14 期伏笔回收。

现象 5:Docker 交付

bash 复制代码
cp .env.example .env          # 填好 DEEPSEEK_API_KEY
docker compose up --build     # 浏览器打开 http://localhost:8000

改几行 web.py,再 docker compose up --build,体会"依赖层缓存命中、只重建源码层"的分层缓存 ------ 这就是"交付"的体感。

五、执行脚本(本期新增完整代码)

完整可运行代码在仓库 code/step15_deploy/ 这个包(前 14 期能力全部在其中)。本期新增的核心 ------ web.py、stream.py、model_router.py(流式调用)、__main__.py(分发)------ 摘录如下:

python 复制代码
#!/usr/bin/env python3
# ↑ step15_deploy/__main__.py(包入口:分发 Web / CLI)

def main():
    if "--cli" in sys.argv:
        from .main import main as cli_main   # 第 14 期命令行入口
        cli_main()
    else:
        from .web import run_web             # 第 15 期默认:Web 服务
        run_web()
python 复制代码
# ↑ step15_deploy/web.py(核心:FastAPI + SSE + 内嵌前端)

app = FastAPI(title="Alex · 第 15 期 Web UI", docs_url=None, redoc_url=None)

_SESSIONS: dict[str, list[dict]] = {}
_ROUNDS: dict[str, int] = {}

def _list_sessions() -> list[dict]:
    """扫 raw_history.jsonl,按 session 聚合会话列表。"""
    ...
def _session_history(session_id) -> list[dict]:
    """读某会话的 user/assistant 消息,用于前端回放。"""
    ...
def _ensure_session(session_id):
    """取(或重建)进程内上下文。重启后从落盘历史回放,做到可续聊。"""
    if session_id in _SESSIONS:
        return _SESSIONS[session_id]
    messages = [{"role": "system", "content": build_system_prompt()}]
    for it in _session_history(session_id):
        messages.append({"role": it["role"], "content": it["content"]})
    _SESSIONS[session_id] = messages
    return messages

@app.post("/chat")
async def chat(payload: dict):
    message = (payload.get("message") or "").strip()
    session_id = payload.get("session_id") or "web-default"
    async def event_stream():
        messages = _get_messages(session_id)
        append_and_persist(messages, {"role": "user", "content": message}, session_id)
        messages[:] = trim_history(messages)
        if _ROUNDS[session_id] > COMPACT_THRESHOLD_ROUNDS:
            messages[:] = compact_history(messages)
        try:
            for ev in stream_chat_with_tools(messages, session_id):
                yield f"data: {json.dumps(ev, ensure_ascii=False)}\n\n"
        except Exception as e:
            yield f"data: {json.dumps({'event': 'error', 'message': str(e)}, ensure_ascii=False)}\n\n"
        yield "data: [DONE]\n\n"
    return StreamingResponse(event_stream(), media_type="text/event-stream",
                             headers={"Cache-Control": "no-cache",
                                      "X-Accel-Buffering": "no"})
python 复制代码
# ↑ step15_deploy/stream.py(核心:流式工具循环,与非流式 chat_with_tools 孪生)

def stream_chat_with_tools(messages, session_id=None):
    while True:
        content_parts, tool_calls = [], {}
        for delta, model_id in llm_call_stream(messages, task="main", tools=TOOLS):
            if delta.content:
                content_parts.append(delta.content)
                yield {"event": "delta", "text": delta.content, "model": model_id}
            if delta.tool_calls:
                for tc in delta.tool_calls:
                    slot = tool_calls.setdefault(tc.index, {"id": "", "name": "", "arguments": ""})
                    if tc.id: slot["id"] = tc.id
                    if tc.function and tc.function.name: slot["name"] += tc.function.name
                    if tc.function and tc.function.arguments: slot["arguments"] += tc.function.arguments
        content = "".join(content_parts).strip()
        if tool_calls:
            ordered = [tool_calls[i] for i in sorted(tool_calls)]
            append_and_persist(messages, {"role": "assistant", "content": content,
                                          "tool_calls": [...]}, session_id)
            for slot in ordered:
                yield {"event": "tool_call",  "name": slot["name"], "arguments": slot["arguments"]}
                fn_args, result = _run_one_tool(slot["id"], slot["name"], slot["arguments"])
                yield {"event": "tool_result", "name": slot["name"], "result": result}
                append_and_persist(messages, {"role": "tool",
                                              "tool_call_id": slot["id"], "content": result}, session_id)
            continue
        append_and_persist(messages, {"role": "assistant", "content": content}, session_id)
        yield {"event": "done", "text": content}
        return

六、总结:十五期,把一台发动机装进了车

核心要点

  1. 壳与核分离:前 14 期的 Agent 能力一行未动,只是从"一个进程里的函数"变成"一个服务里的端点"。Web/CLI 双入口共用同一套内核
  2. SSE 是"打字机"的最小够用方案 :单向下推 + 每轮 done 结束,普通 HTTP + fetch 流式读即可,不必上 WebSocket
  3. 流式工具循环的分片坑 :tool_calls 的 id/name/arguments 分片到达,用 setdefault(tc.index, {...}) 按索引累积分片是"流式+工具"最容易翻车处
  4. 事件溯源一以贯之 :/sessions 聚合、/history 回放、_ensure_session 续聊,全靠前 07 期就开始攒的 raw_history.jsonl ------ Web 层不另起炉灶
  5. 无状态 HTTP + 有状态 Agent :GOAL_STATE 模块级全局变量跨请求存活,是第 14 期伏笔的 Web 版答案

一行代码记住本期

python 复制代码
for ev in stream_chat_with_tools(messages, session_id):
    yield f"data: {json.dumps(ev, ensure_ascii=False)}\n\n"   # Agent 的每个字/动作,流式推给浏览器

十五期全栈串联(课程收官)

期 核心能力 一句话类比
01 认识 Agent + 开工 婴儿出生(能调用 API)
02 连续对话(无记忆) 金鱼记忆
03 短期记忆 history 能记住上一句
04 System Prompt 人设 有身份
05 Tool Use 工具调用 有手
06 Skills 按需加载 有技能库
07 记忆系统(三层) 有脑子(跨会话记得)
08 任务规划 TodoList 有条理
09 子代理 Subagent 有分身
10 Agent Team 团队 有团队
11 MCP 协议 有插座(外部工具即插即用)
12 Hooks 生命周期 有安检(拦截 + 改写 + 审计)
13 多模型路由 有调度台(选模型 + 记成本)
14 目标驱动 Agent 有目标感(订契约 + 自主推进 + 验收)
15 部署上线 + 课程总结 能上路(Web UI + 流式 + Docker)

贯穿始终不变的是三条纪律:事件溯源 (每轮消息产生即落盘,记忆完整可回放)、统一入口 (工具走 execute_tool,LLM 走 llm_call/llm_call_stream,能力走 build_system_prompt)、审计可观测(审计日志管合规,usage_log 管成本,goal_log 管进度)。

适用场景

  • 想把自己写的 Agent 分享给别人用:Web UI 是门槛最低的"产品外衣",打开即用
  • 要"边生成边展示"的对话产品:长回答、写代码、写方案,SSE 打字机体验近乎刚需
  • 团队协作 / 内部工具:Docker 一次构建处处可跑,新同事不用手搓环境
  • 接下来往哪长 :把进程内 _SESSIONS/GOAL_STATE 换进 Redis,给 /chat 加鉴权,把内嵌 HTML 换成正式前端,接入真实多供应商模型 ------ 骨架已搭好,取决于你

结语(不是下期预告,是收官谢幕)

第 15 期讲到这就结束了。这不是"下期预告",而是"收官复盘":15 期,把 Alex 从一个只会调 API 的婴儿,养成了一台能自主完成目标、还能对外服务的 Agent ------ 而最后一期做的事,只是给它装上最后一块外壳:Web UI 把它请进浏览器,SSE 流式给了它打字机的产品体验,Docker 让它从"我机器上能跑"变成"一次构建、处处可跑"。

从零搭建 AI 助手,至此闭环。你可以把 code/step15_deploy/ 当起点继续演进 ------ 发动机和车都有了,接下来往哪开,方向盘在你手里。

感谢各位看官的一路陪伴,大家都再接再厉!

相关推荐
lwx9148521 小时前
Docker-安装SSH服务
docker·容器·ssh
Flynn Liang2 小时前
大模型怎么自己调函数?Spring AI 2.0 Tool Calling 自动循环原理 + 完整时序图
java·人工智能·spring·agent·函数调用·springai·toolcalling
奈落245 小时前
连错一个端口,Agent就“看不见”工具:从 n8n 的8种AI连接类型到 Dify 的插件审批复用,平台设计里的“隐形接线规则”怎么读
人工智能·agent·ai 编程
OpenAnolis小助手5 小时前
龙蜥社区亮相 GOSIM AI Vision Forum 共话“Agentic Native”开源新
人工智能·ai·开源·操作系统·龙蜥社区·agent
时间售卖铺6 小时前
Docker实战:一键部署轻量级容器
运维·docker·云原生
阿里云基础软件6 小时前
Agent 任务提速 10.71%,内存占用降低 21.60%|Alibaba Cloud Linux 4 重大发布
java·linux·人工智能·操作系统·agent
虎鲸不是鱼7 小时前
【虎鲸养殖手册 B007】命令执行的安全细节:白名单不是防弹衣
人工智能·安全·agent·智能体·自进化agent
ZhengEnCi7 小时前
为什么别人的 Skill 不适合你:从「拿来就能用」到「改成自己的流程」的适配方法
人工智能·agent
Liaiyang667 小时前
六家大模型体检卡:六个 ∅_safe(安全兜底态)的藏身之处(或显影处),就是下一次翻车的地方
系统架构·agent·rlhf·ai安全·容错设计·大模型评测·ai幻觉