news 2026/10/2 9:51:39

多回合AI代理开发实战:用Genkit代理API实现工具调用与会话管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多回合AI代理开发实战:用Genkit代理API实现工具调用与会话管理

Genkit的代理API我用了大半年,最深的感受是:它确实把“多回合AI代理”的门槛从框架级降到了配置级。以前要自己写上下文管理、工具调用循环、会话隔离,现在几个API就能串起来。这篇文章我会从零开始,用Genkit的代理API做一个能记住上下文、多轮调用工具完成复杂任务的AI代理,并把我在实际项目中踩过的坑和排查方法一并分享出来,适合刚接触Genkit的开发者,也适合想从“单轮Prompt调用”升级到“真正能干活的多回合Agent”的朋友。

1. 先想清楚:多回合AI代理到底难在哪

1.1 模型本身不记得你上句话说了什么

很多人第一次做AI应用时都会困惑:为什么我调API时把上一轮对话拼进Prompt里,模型还是经常答非所问?原因很简单,大模型API天然是无状态的,它只会根据你这次请求里给的上下文去生成内容。所谓“多回合对话”,本质上是我们开发者自己硬生生把一个连续会话拆成了多次独立的请求,再手动把聊天记录塞回去。

这个“手动塞回去”的动作,第一次写可能觉得还好,不就是把历史消息拼成数组传过去吗?但真正做起来就会发现,要处理的细节远比想象中多:历史消息按什么格式传、太长了要不要截断、用户改了需求后老话还要不要保留、工具调用产生的那一大堆中间结果算不算对话内容……这些都是多回合AI代理的核心问题,也正是Genkit代理API着力解决的部分。

1.2 从“单轮调用”到“多回合代理”,中间隔了四层能力

我习惯把多回合代理拆成四层能力,方便评估一个框架到底帮你做了什么:

能力层要解决的问题没有框架时你需要自己写的代码
对话记忆记住用户之前说了什么自己拼接消息序列、管理截断策略
工具编排模型决定调用工具并返回结果自己解析函数调用请求、执行、回填上下文
状态持久化多个会话互相隔离、可恢复自己设计会话ID、写存储、处理并发
容错机制工具报错、模型循环调用、超时自己写循环上限、异常分支、日志追踪

当你只是做一个“帮我写个文案”的单轮工具时,这些层都不存在;但当你想做一个“能连续追问用户偏好、查天气、算预算、最后给出旅行方案”的Agent时,每一层都会变成真实的代码量。我第一次手动实现这个循环时,光工具调用的正则解析就写了半天,所以后来切到Genkit的代理API后,才意识到框架化解决这些体力活的价值。

1.3 为什么最终选了Genkit的代理API

在选择技术方案时,我当时主要对比了几个主流方向:用LangChain或LlamaIndex、纯手工编排、直接上Genkit。LangChain生态大但抽象层级多,出问题时不容易一眼看穿;纯手工编排最灵活,但多回合会话和工具循环的代码要自己维护,迭代速度慢。

Genkit让我觉得顺手的原因有三个。第一,它把“Agent”视作一等公民,不像其他框架要层层组合Chain、Memory、Tool这些概念,而是直接声明一个Agent,把模型、工具、提示词放进去就行。第二,它的运行时内部已经内置了Agent Loop的调度,模型要调用工具时会自动循环执行,不需要我写while循环。第三,它自带一个开发调试UI,可以看到每一轮消息完整送进模型了什么、工具返回了什么,这对排查多回合问题来说简直是雪中送炭。

2. 项目骨架:从零开始初始化Genkit环境

2.1 安装CLI并初始化项目

我先说操作步骤。Genkit目前对TypeScript的支持最成熟,我的项目也都是用TS写的。新建一个目录后,先初始化npm项目,再安装Genkit CLI和相关依赖。

mkdir trip-agent-demo cd trip-agent-demo npm init -y npm install @genkit-ai/core @genkit-ai/googleai genkit zod

这里解释一下每个包的角色:@genkit-ai/core是核心运行时,@genkit-ai/googleai是Google生成式AI模型的插件(用来接入Gemini系列),genkit提供CLI和开发者工具,zod则是用来声明工具输入输出Schema的,Genkit内部会用它做参数校验和格式约束。

初始化完依赖后,推荐执行一下:

npx genkit init

这个命令会帮你生成基础目录结构和配置文件,如果你完全不想用它的模板,也可以像我一样手动建源码目录。我习惯的结构是:

src/ agents/ # Agent定义 tools/ # 自定义工具 config/ # 模型和插件配置 prompts/ # 提示词文件 output/ # 调试日志输出

2.2 配置模型提供方:云端和本地模型怎么切换

Genkit的一大特点是可以配置多个模型提供方,开发时和上线时用不同模型很常见。我的配置通常长这样:

import { genkit } from '@genkit-ai/core'; import { googleAI, gemini } from '@genkit-ai/googleai'; import ollama from 'genkitx-ollama'; export const ai = genkit({ model: gemini('gemini-2.0-flash'), plugins: [ googleAI({ apiKey: process.env.GEMINI_API_KEY }), ollama({ servers: [{ url: 'http://localhost:11434' }] }), ], });

注意genkitx-ollama这个包名,它是Genkit的Ollama插件,让你能在本地跑Llama、Qwen这类开源模型。这个能力在开发调试阶段特别有用,因为本地模型没有成本,可以在初期疯狂测试Agent行为,等确定逻辑没问题了再切到云端模型跑正式场景。我建议所有刚开始接触Genkit的人都先装一个Ollama,把模型配成ollama/llama3.1之类的本地模型,而不是直接用云端API,这样测试循环会快很多,也省掉不必要的费用。

2.3 Dev UI:多回合调试的利器

Genkit装好后,运行npx genkit start会启动一个本地开发服务器,默认地址是http://localhost:4000。这个页面我愿称之为“多回合Agent事故现场还原器”。它能看到什么?最核心的是每次请求的完整轨迹,包括:

  • 用户输入到底发给了模型什么内容
  • Agent决定调用了哪个工具
  • 工具返回了什么原始结果
  • 模型拿到工具结果后又生成了什么

有一次我的Agent在第三轮对话时突然开始胡说八道,我翻代码半天没找到原因,结果打开Dev UI一看,发现是历史消息里混入了一条工具返回的原始JSON,模型被那一段JSON带偏了。这种问题不通过完整的追踪视图排查,光靠猜prompt配置不知道要猜到什么时候。

3. 核心实现:用代理API搭建旅游规划助手

3.1 先定义三个工具

我这次要做的示例是一个旅游规划助手,名头听起来复杂,但实际上只需要三个工具:查天气、算预算、推荐景点。工具的意义在于让Agent拥有超越“文本生成”的能力——它能当作“手脚”去获取真实信息。

先定义天气工具:

import { z } from 'zod'; export const getWeather = ai.defineTool( { name: 'getWeather', description: '查询指定城市未来3天的天气情况,用于行程规划时判断是否需要带雨具、调整户外安排', inputSchema: z.object({ city: z.string().describe('城市名称,如:大理、丽江'), }), outputSchema: z.object({ city: z.string(), forecast: z.array( z.object({ date: z.string(), condition: z.string().describe('天气状况,如晴、多云、小雨'), highTemp: z.number(), lowTemp: z.number(), }) ), }), }, async ({ city }) => { // 实际项目中这里替换为真实天气API return { city, forecast: [ { date: '2025-02-01', condition: '晴', highTemp: 18, lowTemp: 6 }, { date: '2025-02-02', condition: '多云', highTemp: 16, lowTemp: 7 }, { date: '2025-02-03', condition: '小雨', highTemp: 14, lowTemp: 5 }, ], }; } );

再定义预算工具:

export const calculateBudget = ai.defineTool( { name: 'calculateBudget', description: '根据目的地、天数和人均预算,计算一个旅行团的总预算范围,返回交通、住宿、餐饮、门票各项估算', inputSchema: z.object({ destination: z.string(), days: z.number(), travelers: z.number(), perPersonBudget: z.number().describe('每人总预算,单位元'), }), outputSchema: z.object({ totalRange: z.object({ min: z.number(), max: z.number() }), breakdown: z.record(z.string(), z.string()), }), }, async ({ destination, days, travelers, perPersonBudget }) => { // 简化逻辑:预算拆分的小逻辑,真实场景可能查询价格数据库 const perDay = perPersonBudget / days; const min = perPersonBudget * travelers * 0.9; const max = perPersonBudget * travelers * 1.15; return { totalRange: { min: Math.round(min), max: Math.round(max) }, breakdown: { 交通: `往返交通约${Math.round(perDay * 0.3)}元/人/天`, 住宿: `住宿约${Math.round(perDay * 0.35)}元/人/天`, 餐饮: `餐饮约${Math.round(perDay * 0.2)}元/人/天`, 门票: `门票及其他约${Math.round(perDay * 0.15)}元/人/天`, }, }; } );

再补充一个景点推荐工具:

export const getAttractions = ai.defineTool( { name: 'getAttractions', description: '获取指定城市的Top 5景点列表,每个景点附带建议游玩时长和门票参考价', inputSchema: z.object({ city: z.string(), }), outputSchema: z.object({ attractions: z.array( z.object({ name: z.string(), duration: z.string(), ticket: z.string(), }) ), }), }, async ({ city }) => { // 实际项目中替换为景点数据库或API return { attractions: [ { name: '古城漫步', duration: '3小时', ticket: '免费' }, { name: '苍山索道', duration: '4小时', ticket: '120元' }, { name: '洱海骑行', duration: '半天', ticket: '80元(租车)' }, ], }; } );

我特意在每个inputSchema里加了.describe(),这个细节很重要。模型决定是否调用工具、传入什么参数时,依赖的就是这段描述。你描述越具体,模型传错参数的概率就越低。比如description里写了“用于行程规划时判断是否需要带雨具”,模型看到一个用户说“我担心下雨”,就知道可以调用这个工具来查天气。

3.2 用defineAgent定义多回合代理

工具准备好了,现在用Genkit的代理API把Agent定义出来。这一步是整个项目的核心:

export const tripPlannerAgent = ai.defineAgent( { name: 'tripPlanner', description: '旅游规划助手,负责收集用户旅行需求并调用工具查询真实信息', model: gemini('gemini-2.0-flash'), tools: [getWeather, calculateBudget, getAttractions], prompt: ` 你是一位经验丰富的旅游规划助手。你会通过多轮对话逐步了解用户的旅行需求。 在对话过程中,你必须遵守以下规则: 1. 当用户提到想去某个城市但你没有天气信息时,调用 getWeather 查询。 2. 当用户提到预算、人数、天数等信息时,调用 calculateBudget 计算大致花费。 3. 当用户需要景点推荐时,调用 getAttractions 获取真实景点列表。 4. 如果用户提供的信息不足(比如只说了目的地没说天数),先用自然语言追问,不要急着调用不完整的工具。 5. 最终回答要给出一个整合的、可执行的行程方案,并注明哪些数据来自工具查询。 `, }, async (input, { messages, session, generate }) => { // 这里可以在Agent自动处理之余,插入自定义逻辑 const userMessage = input; return { message: userMessage }; } );

这里可能有人会问:为什么defineAgent的handler里好像没写什么东西,Agent不还是能处理多回合对话吗?这就是Genkit代理API设计的巧妙之处——它把“多回合记忆累积”和“工具调用循环”都放在了框架内部,在handler被调用前,Agent已经自动把历史消息接上了。handler只负责把用户的最新输入变成消息内容,然后Agent运行时就会带着完整上下文继续生成。

如果你有自定义逻辑需求,比如要在某一类输入时强行修改用户消息,或者要追加一条系统提示,可以在handler里操作session或messages,后面我会专门讲会话管理。

3.3 系统提示词:告诉Agent什么时候该问、什么时候该做

很多人在Agent上效果不好,90%的锅并不在模型,而在系统提示词写得太含糊。你要明白,模型本身并不知道你的产品规则是什么,它只有通过提示词才能理解“何时该调用工具、何时该先反问用户”。

我总结了一个提示词三段式写法,你在自己的Agent里也可以直接套用:

  • 第一段:说明身份和任务边界,让模型知道自己是“旅游规划助手”,不是“诗词生成器”。
  • 第二段:用“当……时,调用……”的句式,明确绑定触发条件,这是最有效的手段。
  • 第三段:写清楚“禁止”或“避免”的行为。比如“信息不足时不要直接瞎猜”,这种负面约束往往比正面命令更关键。

再补充一个细节:提示词里出现工具名时,要和defineTool里的name保持一致。如果你在提示词里写了getWeatherInfo,但工具定义叫getWeather,模型可能就会产生一个错误调用的幻觉,Genkit会直接报校验失败。

3.4 完整对话流程拆解:它到底是怎么“多回合”的

我把一次真实的对话过程走一遍,你能清楚地看到多回合代理和普通问答的差异。

用户第一句话说:“我想带家人去云南玩五天,预算大概两万左右。”

Agent收到后,内部流程是这样的:先检查自己手上的已知信息——目的地“云南”太宽泛了,五天、两万、带家人这些信息已经有了,但“家人”有几个人?云南那么大,具体去哪个城市?天气如何?预算算出来是否合理?这些都没有。于是模型根据系统提示词里的第4条规则,选择不调用工具,而是生成反问:“你们一共几位?更偏向大理、丽江还是西双版纳那种风格?”

用户第二句话回答:“四大一小,想去大理和丽江,主要想看风景。”

这时Agent得到的完整上下文是“第一轮对话内容 + 第二轮新输入”。模型判断:目的地比较明确了,可以去查天气;人数5人、天数5天、人均预算4000元也能算预算了。于是它发起工具调用:先调getWeather查大理和丽江的天气,再调calculateBudget算5人5天预算,还调用了getAttractions获取景点列表。

Genkit的运行时会把这三个工具请求依次执行,把结果作为“工具消息”送回给模型。模型看过天气、预算、景点数据后,最终组织出一份完整行程建议,并明确引用“根据计算,预算范围大约在……”这类表述。

你发现没有,从用户角度看这只是多说了几句话,但系统内部已经完成了“理解需求 -> 反问补全 -> 并行调用多个工具 -> 汇总推理”的完整链路。如果没有代理API,这一大段逻辑全得自己手写。

4. 会话记忆与状态管理:让Agent记住上一轮的事

4.1 Genkit的会话模型是怎么工作的

多回合代理最核心的性质就是跨轮次记忆。Genkit里有一个边界清晰的抽象:一个session代表一段完整的对话,每个会话都有唯一的sessionId。在同一个sessionId下,所有消息会被自动累积并跟随请求发送给模型。

你可以把session想象成一个“聊天窗口”,只是这个窗口不是在手机上滑动,而是存在服务端。当用户刷新页面、断网重连,甚至换了一台设备,只要请求带上同一个sessionId,Agent就能接着之前的对话继续。

在我自己的实现里,通常是这样创建会话的:

const sessionId = req.body.sessionId ?? `trip-${Date.now()}`; const response = await tripPlannerAgent.run({ input: { message: userMessage }, sessionId, });

这里有个我早期踩过的坑:如果你每次请求都不传sessionId,Agent会默认创建一个新会话,等于用户每句话都被当成一段全新对话,上下文当然就“失忆”了。很多新手说多回合Agent不生效,八成就是漏了这个参数。

4.2 会话里不只是消息,还可以存自定义状态

除了聊天记录,session还能保存开发者自定义的状态数据。这个功能特别适合用来存Agent在对话中“积累出来的结论”。举个例子,用户一开始可能说自己喜欢安静、不喜欢商业化的景点,这个偏好你可以塞进session.state,之后每一轮Agent都能读取,比每次都让模型从历史消息里自己找更高效、更稳定。

// 在Agent handler里读写session状态 async (input, { session, messages }) => { const prefs = session.state.preferences ?? {}; if (input.message.includes('安静')) { session.state.preferences = { ...prefs, vibe: 'quiet' }; } // 后续逻辑可以基于session.state做判断 return { message: input.message }; }

我在实践中的体会是:session.state适合放“结构化、需要稳定使用的信息”,比如用户ID、偏好标签、上一步操作的意图;而“自然语言的完整聊天过程”就交给消息历史去管。两者配合,Agent才能既保持灵活性又足够稳定。

4.3 把聊天记录存到外部存储,才能真正上线

默认情况下,Genkit的会话数据存在内存里。内存存储对开发调试没问题,但一旦服务重启或有多实例部署,就会丢上下文。生产环境我建议至少要做这两个改造之一:

一是Redis存储。Genkit允许你自定义会话存储实现,Redis是最常见的方案,因为它的SETEX天然适合给会话设置过期时间。每次Agent运行时,先按sessionId从Redis拉历史消息,跑完后把新消息写回去。这样即使用户隔两天再回来,也能接着聊。

二是数据库落库。如果项目有现成的Postgres或MySQL,我倾向于把完整消息序列和session.state落库。缺点是每次请求都要读库,延迟会比Redis高一些;优点是方便后续做用户行为分析、审计回溯。

如果你不想自己维护存储,Genkit官方还提供了一些开箱即用的方案,具体可以根据你用的后端存储选插件。但我要提醒一点:无论选哪种外部存储,一定要把session.state和消息历史看成一个整体去持久化,缺少任何一部分都会导致会话恢复后行为异常。

5. 工具循环与编排细节:Agent Loop的台前幕后

5.1 Agent Loop的完整生命周期

为什么模型能自动决定调用什么工具?这背后的机制叫Agent Loop,也叫工具调用循环。我把它拆成七个步骤,你看一遍就明白:

  1. 系统提示词和历史消息聚合,作为输入送进模型。
  2. 模型生成回复。如果它觉得不需要调用工具,生成的就是最终文本,循环结束。
  3. 如果模型觉得需要外部信息,它不会直接写文本,而是生成一个结构化的“工具调用请求”,里面包含工具名和参数。
  4. Genkit解析这个请求,校验工具名是否存在、参数是否符合Schema,然后把请求派发给对应的工具函数。
  5. 工具函数执行,返回结构化结果。
  6. 这个结果被包装成一条“工具消息”,追加到会话上下文中,再次送回给模型。
  7. 回到第2步,模型继续生成——它可能输出最终答案,也可能继续调用下一个工具。

这个循环对开发者是透明的,所以你写defineAgent时完全不用手写循环。我第一次跑通这个流程时,嘴里冒出的第一句话就是“原来如此”。

5.2 怎样防止Agent陷入无限循环

Agent Loop虽然方便,但有一个所有框架都逃不掉的问题:模型在Tool Call循环里出不来。比如它反复调用同一个工具,参数一样,结果一样,却始终不生成最终回答,看起来就像程序死循环了。

Genkit提供了配置参数来限制循环次数,我在项目中通常会显式设置:

const ai = genkit({ model: gemini('gemini-2.0-flash'), }); // 在定义Agent时可以配置 export const tripPlannerAgent = ai.defineAgent({ name: 'tripPlanner', tools: [getWeather, calculateBudget, getAttractions], model: gemini('gemini-2.0-flash'), maxIterations: 8, // 限制工具调用循环最多8次 timeout: 120000, // 单次运行整体超时,单位毫秒 });

maxIterations设得太大浪费token和时间,设得太小又可能导致Agent还没查完数据就被打断。我做过的项目里,纯工具类Agent设8到10次比较稳,涉及多步骤任务可以稍微放宽到15次。另外,超时配置也要按场景调:如果工具本身要调外部HTTP接口,单次超时建议至少30秒,整体超时就按“工具次数 x 平均工具耗时”来估。

5.3 多工具协作时的两个坑

第一个坑是参数串味儿。模型在多个工具间做调度时,如果工具Schema中的字段名有重合,比如getWeather用了city,getAttractions也用了city,模型倒不至于搞混;但如果你把第二个工具的参数命名为place、location、destination三套叫法,模型就会开始犯错。我的经验是,所有工具的入参字段尽量用同一套命名体系,比如全用destination,这样模型泛化更强。

第二个坑是结果过大。工具返回的数据如果冗长,会让整个上下文迅速膨胀。比如真实天气API可能返回每小时一条数据,连续15天的量很大。我建议工具函数在返回前就做好精简,只返回Agent真正需要生成结论的字段。必要时可以在工具内部把原始数据聚合好,再返回一个精简摘要。

6. 真刀真枪的坑:常见问题与排查实录

6.1 对话到第三轮就“失忆”了

这是一个出现频率极高的问题,现象是前两轮聊得好好的,第三轮问Agent“我刚才说的那个城市你记得吗”,它一脸茫然。排查思路按下面的顺序来:

  • 确认每次请求是否传了同一个sessionId。这是最常被忽略的。用日志把每轮请求的sessionId打出来,一眼就能看清楚。
  • 如果在本地用Dev UI测试,Dev UI默认会帮你管理会话,但你自己通过API调用时就要显式传sessionId。
  • 确认会话存储没有被清空。如果跑在无状态服务器上,重启后内存存储就没了,现象就是“隔一会回来就失忆”。解决方法是接外部存储,参考4.3节。
  • 检查消息历史长度。上下文超过模型窗口限制时,Genkit会触发截断策略,如果配置不当,截断后的上下文恰好丢掉了早期关键信息。

6.2 工具参数“它老编错”

现象是工具调用时传入了明显不对的参数,比如用户问“大理天气”,Agent却调了getWeather并传入{ city: '云南' },或者把字符串传给了数字字段。这类问题的根因基本有三个:

一是工具描述和Schema描述太模糊。我前面提到过,inputSchema里的每个字段都要写清楚.describe(),并且description里要写清楚工具的作用边界。这一步做扎实,90%的参数错误都能解决。

二是提示词里出现冲突信息。比如你在工具定义里说参数city必须传具体城市,但在自定义提示词里又写了“云南地区可以统一查一个代表城市”,这就会诱导模型乱来。提示词和工具描述要互不冲突。

三是某些模型底座的工具调用能力本来就弱。如果你用本地小模型,遇到工具参数总是错,先别怀疑代码,试着把温度调到0或0.1,再看看是不是模型本身的问题,必要时切回更强的模型。

6.3 多会话串号,生产环境的会话隔离

多回合代理上线后最容易出的“事故”就是A用户问的问题,B用户看到了自己的回答里混进了A的内容。这个问题的本质是sessionId在请求之间没有做严格隔离。

我的经验是在服务入口统一处理:从请求头或授权信息中解析用户身份,再生成或者映射到对应的sessionId,而不是直接信任前端传过来一个自定义的sessionId。前端传来的sessionId只能作为辅助标识,不能作为唯一的会话凭据。否则,只要有人随手把自己的sessionId改成别人的,就能看到别人对话的完整上下文。

6.4 调试三件套:日志、Dev UI、模拟数据

最后分享我排查Genkit多回合Agent问题的“三件套”套路,适配率高到惊人。

第一步,先在代码里打开详细日志。Genkit支持logLevel: 'debug',开启后会在终端打印每个请求的详细内部信息,包括消息数、上下文内容、工具调用过程。看到这个日志,你基本就知道Agent“脑子里”在想什么了。

第二步,如果日志信息还不够直观,打开Dev UI,按照时间线模式查看完整交互过程。点击某一条消息,能看到模型收到的实际Prompt长什么样、工具返回的原始JSON是什么。排查上下文污染、提示词冲突这类问题时,这个可视化视图几乎是唯一高效的手段。

第三步,用一套固定的模拟数据做回归测试。我建议为Agent准备几个标准测试案例,比如“用户只说目的地,看Agent会不会追问”、“用户直接给全部信息,看Agent会不会直接出方案”、“用户中途改预算,看Agent会不会更新计算”。每次改动提示词或工具后,都跑一遍这些案例,比临时手动随机对话高效得多。

最后再分享一个我个人的使用习惯:构建多回合Agent不要一上来就追求复杂工具集,先让Agent在只有一两个工具的约束下把“多轮对话 + 上下文累积 + 简单工具调用”跑通,再逐步加工具。每加一个工具,都要测试它在“信息不足”“参数错误”“结果异常”三种情况下的表现是否可控。这样迭代下来,你的Agent稳定性会比一次性写完十来个工具再调试高很多。Genkit的代理API最大的价值不是减少你写代码的行数,而是让你可以把精力放在真正影响用户体验的地方——提示词的质量、工具返回的准确性、会话状态的灵活性。这些才是多回合AI代理产品力的核心。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 9:51:21

AI知识库不只是搭个RAG:从Demo到生产级系统的关键挑战

“AI知识库是什么?不就是搭个RAG?”这句话我在过去一年里听了不下十遍。说真的,每次听到我都挺感慨——三个月前搭了个RAG demo,上传几份PDF能对话了,就觉得自己已经把AI知识库做完了。直到业务同学把几百份带扫描签章…

作者头像 李华
网站建设 2026/10/2 9:50:46

Meta挖角MongoDB前CEO:企业级AI的胜负手是数据基础设施

昨天看到一条消息:Meta把MongoDB的前任CEO Dev Ittycheria挖了过去,让他负责AI基础设施方向。第一反应是——一个做数据库的,去Meta搞AI,能干什么?再往下想一层,这一轮Meta的企业级AI布局里,自家…

作者头像 李华
网站建设 2026/10/2 9:50:27

CSS列表符号深度解析:从ul/li样式控制到高定制化实战

1. 为什么一个小小圆点值得花时间深究&#xff1f;你有没有遇到过这样的场景&#xff1a;页面上一个<ul><li>列表&#xff0c;设计师发来的UI稿里&#xff0c;项目符号不是默认的实心圆&#xff0c;而是一个带描边的空心圆、一个蓝色箭头、甚至是一枚小小的图标&am…

作者头像 李华
网站建设 2026/10/2 9:50:20

鲁泰建材穿孔吸音硅酸钙板 12mm多功能板 适用于影剧院声学装修

穿孔吸音硅酸钙板成为影剧院声学装修的主流选择近年来&#xff0c;随着文化娱乐产业的快速复苏与公共建筑标准的不断提升&#xff0c;影剧院、音乐厅、多功能厅、报告厅等观演类建筑项目在全国范围内持续增多。此类建筑对室内声学环境要求严苛&#xff0c;既要控制混响时间、降…

作者头像 李华
网站建设 2026/10/2 9:49:05

AI从工具到科研搭档:产业研发人机协同落地指南

1. 先搞清楚&#xff1a;工具与搭档&#xff0c;差的不是智商而是协作契约 这些年我深度参与过不少产业研发项目&#xff0c;一个观察越来越强烈&#xff1a; 很多团队对AI的期待还停留在“高级计算器”阶段——给指令、拿结果、不满意就重来。但真正让研发效率发生质变的&…

作者头像 李华
网站建设 2026/10/2 9:49:03

Hindsight Dify实战:构建带回溯反思的智能复盘工作流

“当时到底是怎么想的&#xff1f;”这句话&#xff0c;我几乎每天都会在项目复盘会上听到。普通对话中这叫“事后诸葛”&#xff0c;但在AI应用开发里&#xff0c;它有个更精确的英文词&#xff1a;hindsight。直译是“后见之明”&#xff0c;放到大模型落地的语境里&#xff…

作者头像 李华