news 2026/10/31 10:21:04

告别Tool Calling:用状态机与Schema打造稳定结构化Agent

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
告别Tool Calling:用状态机与Schema打造稳定结构化Agent

这个系列写到第五篇,前几篇我一直在折腾 LangChain、Dify 这类框架,也在各种场景里试过 Tool Calling。说实话,在本地模型上被 Tool Calling 的格式错误折腾到怀疑人生之后,我开始重新审视一个问题:真正需要 Tool Calling 的 Agent 场景,到底占多大比例?答案是——比我最初以为的少得多。

这篇文章要聊的是一个完全不依赖 Tool Calling 的结构化通用 Agent。它不注册任何工具、不解析 function call 协议,却能把任意文本输入转换成符合指定 schema 的结构化数据。它的核心思路是:把 Agent 的内部流程拆成"理解、规划、推理、结构化、校验"几个阶段,用状态机驱动,用 Pydantic 定义数据契约,用重试机制兜底。如果你正在做信息抽取、文档解析、文本分类、数据清洗这类任务,或者觉得 Tool Calling 在开源模型上太不稳定,这篇文章应该能给你一个全新的落地思路。它适合有一定 Python 基础、做过一两个 Agent 项目的读者,不需要你有深厚的框架经验都能读懂。

1. 为什么我不再迷信 Tool Calling:一次现场演示引发的重构

1.1 Tool Calling 的四大隐性成本

我当时做的是一个客服工单分类 Agent,计划通过 Tool Calling 把工单内容转成结构化标签,再调用一个"工单管理函数"写入系统。演示前一天还在云端模型上跑得挺好,到了客户现场换成内网部署的 7B 量化模型,Tool Calling 连续三次返回了错误的 function call 格式——不是多了一个参数,就是参数名对不上 schema,甚至有一次直接返回了一段自然语言而不是函数调用指令。那一刻我意识到,Tool Calling 这套机制的成本被我严重低估了。

第一个成本是格式稳定性。Tool Calling 依赖模型输出高度格式化的 function call 结构,这对模型能力要求很高。云端旗舰模型没问题,开源小模型尤其是量化版就很脆弱。第二个成本是函数定义的重复劳动。每接一个新任务,你得重新定义函数、参数、枚举值,这些工作看起来简单,实际维护起来非常烦。第三个成本是调试链路长。工具调用出问题时,你很难判断是模型理解错了、参数传错了、还是执行结果返回有问题。第四个成本是很多任务根本不需要工具。比如从合同里抽取甲方、乙方、金额,模型看一眼就能直接输出 JSON,压根不需要调用任何外部函数。

1.2 真正需要工具和无需要工具的 Agent 场景边界

在做这个项目之前,我习惯性默认"Agent 一定需要工具",后来认真盘点了一下之前做过的所有 Agent 需求,发现差不多七成场景根本不涉及外部动作。它们本质上是"信息处理"类任务:给我一段文本,按我的要求输出结构化结果。

这类任务有四个共同特征:输入是文本或文档,输出是结构化数据,中间过程不调用外部 API 或数据库,正确性可以通过 schema 校验来衡量。典型例子包括合同要素抽取、简历解析、用户反馈分类、财报摘要结构化、工单标签提取。而真正需要 Tool Calling 的场景,往往有一个硬性特征:Agent 必须主动发起外部动作——查数据库、发 HTTP 请求、执行代码、操作文件。没有这个动作,任务就无法闭环。

想清楚这条边界之后,我就决定做一个专门服务"信息处理类"任务的 Agent,它不需要 Tool Calling,但必须足够结构化、足够通用。这样既能绕开开源模型在 function call 上的稳定性问题,又能把注意力集中在真正有价值的部分——让模型输出稳定可靠的结构化数据。

1.3 结构化通用 Agent 的定位:一条 schema 驱动的智能数据管道

这个 Agent 的定位,我想了很久,最后用一句话概括:它是"一条 schema 驱动的智能数据管道"。输入是原始文本,输出是符合你定义 schema 的 Python 对象。中间的"智能"部分由大模型承担,但模型不决定调用什么工具,它只决定怎么做推理。

就像传统的数据管道用 XSD 或 JSON Schema 约束数据格式一样,这个 Agent 用 Pydantic 模型定义输出契约。只要你换一个 Pydantic 模型,它就换一个任务。同一个 Agent 实例,昨天在抽合同要素,今天可以转成做简历解析,代码一行都不用改。这种通用性是我之前用 Tool Calling 做方案时从来没体会过的。

2. Agent 内核设计:状态机推进与无工具推理

2.1 整体运行流程:Init → Plan → Reason → Struct → Validate

没有 Tool Calling 之后,Agent 的工作方式变成了一条清晰的状态机链路。整个流程我拆成了五个状态:Init、Plan、Reason、Struct、Validate。Init 阶段接收任务描述、输入文本和输出 schema;Plan 阶段让模型理解任务并拆解要点;Reason 阶段让模型自由输出中间分析;Struct 阶段要求模型把中间分析整理成符合 schema 的 JSON;Validate 阶段用 Pydantic 校验,失败则带着错误信息回到 Struct 重试。

为什么要拆成五个状态而不是直接让模型输出最终 JSON?因为模型在"边推理边格式化"的时候,往往会顾此失彼。你让它直接输出结构化结果,它可能忽略了一些推理细节;你让它先分析再结构化,两步各干各的,效果会稳定很多。这就是我放弃 one-shot 输出,改走状态机的最直接原因。

from enum import Enum class AgentState(str, Enum): INIT = "init" PLAN = "plan" REASON = "reason" STRUCT = "struct" VALIDATE = "validate" DONE = "done" FAILED = "failed"

状态机的好处不只是流程清晰,更在于每个状态都可以单独设置 prompt 策略、单独记录日志、单独做容错。比如 Reason 阶段失败和 Struct 阶段失败的处理方式完全不同——前者重跑一次推理即可,后者需要带上具体的 schema 校验错误去修正格式。

2.2 规划阶段:让模型自己拆解输入要点

Plan 阶段的 prompt 设计很关键。我不会让模型做任何输出,只是让它"阅读输入并列出关键信息点"。这一步相当于给模型一个缓冲地带,强迫它把注意力放在理解输入上,而不是急着套模板。

这里有一个使用技巧:给 Plan 阶段设定一个输出长度上限,要求模型用要点列表输出,每条不超过 20 个字。加了这个限制之后,后续 Struct 阶段的准确率有明显提升。原因不复杂——模型在规划阶段被迫做了信息筛选,进入结构化阶段时已经完成了"泛读到精读"的过渡。

2.3 推理阶段:无工具如何保证"计算"正确

很多人会疑惑:不调用工具,Agent 怎么保证计算逻辑正确?这里的"计算"要分情况讨论。如果任务是数字计算,比如根据合同金额和税率算含税总价,我确实会建议你用代码。但这类"Agent 实践"里的绝大多数任务,推理并不是算术计算,而是语义推理——判断一段话是投诉还是咨询、识别合同里的违约责任条款、给一份技术文档打出来三个主题标签。

语义推理恰恰是模型最擅长的事。Reason 阶段的 prompt 我会写得很宽松,核心指令就一句话:"基于规划阶段的信息点,给出你的推理过程和结论,用自然语言即可。"这一步不设置任何格式约束,目的是让模型把脑子里的分析过程完整写出来。后续 Struct 阶段再把这些分析"翻译"成结构化数据。

2.4 为什么要用状态机而不是直接 one-shot 输出

直接 one-shot 输出在 Demo 里看起来很酷,实际跑两三百条真实数据就会发现,格式质量和内容质量都不稳定。状态机的价值恰好在这两种质量上都有体现:格式质量上,先推理后结构化的模式天然降低了模型"边想边写"导致的字段遗漏;内容质量上,规划阶段的强制信息筛选减少了幻觉——模型在 Reason 阶段没有说过的内容,到了 Struct 阶段基本也不会瞎编。

一句话总结:状态机的本质是把"一个困难的生成任务"拆成"几个简单的生成任务",而模型在简单任务上的表现远好于困难任务。这个原则对所有 Agent 设计都适用,尤其是资源受限的本地部署场景。

3. 结构化输出的两层保障:JSON Schema 与运行时校验

3.1 第一层:用 Pydantic Schema 生成模型可读的 JSON Schema

结构化输出的第一层保障,是让模型"看到"精确的 JSON Schema。我用 Pydantic 定义输出模型,然后在运行时调用model_json_schema()生成 JSON Schema,再把它嵌入 prompt。这一步极其关键,模型看到的是一个明确的字段契约:字段名、类型、是否必填、每个字段的说明。

from pydantic import BaseModel, Field from typing import Optional class ContractExtraction(BaseModel): party_a: str = Field(description="甲方完整名称") party_b: str = Field(description="乙方完整名称") amount: Optional[float] = Field(default=None, description="合同总金额,无法提取则为null") currency: str = Field(default="CNY", description="币种") payment_terms: list[str] = Field(default_factory=list, description="付款条款要点列表") liabilities: list[str] = Field(default_factory=list, description="违约责任要点列表")

Field 的 description 一定要写清楚,因为这部分会直接出现在 JSON Schema 的description字段里,模型全靠它理解每个字段的业务含义。我们做信息抽取时,description 的质量直接影响抽取准确率。比如amount字段如果只写"金额",模型可能把税率、利息也算进去;写上"合同总金额,无法提取则为null"之后,误抽取概率就降下来了。

3.2 第二层:零样本之外的 few-shot 与输出约束

JSON Schema 本身是零样本引导,效果在多数场景下够用,但偶尔会遇到模型对某个字段的理解有偏差。这时候我会在系统 prompt 里加一个 few-shot 示例,示例包含一段输入片段和对应的完整 JSON 输出。

FEW_SHOT_EXAMPLE = """输入片段: 甲方:北京云帆科技有限公司(以下简称乙方)与上海远航物流有限公司(以下简称甲方)...... 正确JSON输出: { "party_a": "上海远航物流有限公司", "party_b": "北京云帆科技有限公司", "amount": 1280000.0, "currency": "CNY", "payment_terms": ["合同签订后3日内支付30%", "验收合格后30日内支付70%"], "liabilities": ["乙方逾期交货按日万分之三支付违约金"] } """

注意 few-shot 里故意设计了一个坑:输入里"北京云帆科技有限公司"后面跟着"以下简称乙方",所以它其实是乙方而不是甲方。用这种带陷阱的示例,能让模型更谨慎地分析主体关系,而不是看到"科技公司"就打上甲方的标签。

除此之外,如果后端支持,我还会开启 JSON mode 或response_format={"type": "json_object"},这相当于加了第三层格式保险。实测下来,OpenAI 兼容接口支持这个参数的非常多,本地部署的 vLLM、Ollama 也都兼容。

3.3 运行时校验与自动重试机制

Prompt 约束得再好,也不能完全保证模型输出合法 JSON。所以运行时校验这层保险必须做,而且要做到位。具体做法是:把模型返回的字符串用json.loads解析,然后把结果交给 Pydantic 的model_validate做类型和必填项校验,任何一步失败都会进入重试流程。

import json from pydantic import ValidationError def _validate(raw_output: str, schema): try: data = json.loads(raw_output) return schema.model_validate(data) except (json.JSONDecodeError, ValidationError) as e: return None, str(e)

重试的关键在于,你不能只是简单地说"格式不对,请重试",而要把具体的错误信息反馈给模型。JSON 缺了哪个字段、哪个字段类型不对、哪一段没按 JSON Schema 来,全都拼进重试 prompt。模型看到具体错误后,修正的成功率非常高。

RETRY_TEMPLATE = """你上一次的输出没有通过校验,错误信息如下: {error_message} 请严格按原始 JSON Schema 重新输出,不要附加任何其他内容。"""

我给重试次数设了上限,默认是 2 次。超过 2 次还没通过,就标记为FAILED状态,把原始输出和错误信息一起记录下来手工处理。为什么不是无限重试?因为多数情况下失败是模型能力实在达不到,而不是偶然失误,再重试也只是浪费时间。设定上限反而逼着你去优化 schema 定义或 prompt 约束,而不是把所有问题甩给重试。

4. 通用性设计:模型无关、任务无关的接口抽象

4.1 用 Schema 作为任务契约,而不是硬编码字段

这个 Agent 之所以"通用",第一点就是把 Pydantic Schema 当作外部契约,而不是把字段硬编码在业务逻辑里。我的run()方法接收一个schema参数,运行逻辑完全不知道也不关心要输出哪些字段。你传一个ContractExtraction,它就抽合同要素;你传一个ResumeProfile,它就解析简历;你传一个FeedbackCategory,它就做文本分类。

本质上是把"任务差异"和"执行逻辑"彻底解耦。任务差异完全由 schema 表达,执行逻辑只关心"如何让模型产出符合 schema 的 JSON"。这样带来的直接好处是:新需求来时,不需要写新 Agent,只需要定义一个 Pydantic 模型。

4.2 模型后端抽象:OpenAI、Ollama、vLLM 一套逻辑全接入

通用性的第二点是模型无关。我用一个极简的LLMBackend抽象接口把各家模型接起来,核心方法就一个complete(messages, temperature)。

from typing import Protocol class LLMBackend(Protocol): def complete(self, messages: list[dict], temperature: float = 0.2) -> str: ...

OpenAI 兼容实现是最常用的,因为无论 OpenAI 官方、Ollama、vLLM、还是各类网关,都支持这个协议,一套代码到处跑。

from openai import OpenAI class OpenAICompatBackend: def __init__(self, base_url: str, api_key: str, model: str): self.client = OpenAI(base_url=base_url, api_key=api_key) self.model = model def complete(self, messages: list[dict], temperature: float = 0.2) -> str: resp = self.client.chat.completions.create( model=self.model, messages=messages, temperature=temperature ) return resp.choices[0].message.content

类里面我特意设置了temperature=0.2,结构化生成任务温度要偏低,否则同一份输入每次跑出来的字段都会有波动。如果你想接 Anthropic 或者其他非 OpenAI 兼容服务,只需要再实现一个complete()方法即可,Agent 核心代码完全不用动。

4.3 配置化运行:一份配置跑完一个任务

为了让通用性真正落地,我把 Agent 的运行参数做成了配置化。一个任务对应一份 YAML 配置,包括模型后端、模型名称、temperature、系统 prompt、few-shot 示例、重试次数。实际使用中,我只需要写一份新 schema 和一份新配置,就能在一个通用引擎上跑一个新任务。

task: contract_extraction backend: openai_compat model: qwen2.5:14b temperature: 0.2 max_retries: 2 system_prompt: | 你是一名专业的法律合同要素抽取助手。 基于输入的合同文本,提取关键要素并输出为指定 JSON Schema。 few_shot: - input: 输入片段... output: 正确JSON输出...

这种"配置 + Schema"双驱动的设计,让我从"每个任务写一个脚本"变成了"每个任务写一份配置"。维护成本低了很多,团队其他人接手也容易上手。

5. 完整代码实现与实测效果

5.1 项目结构与核心类

整个实现我用了一个类StructuredAgent,加上一个LLMBackend抽象、一个状态管理器。这里给出最核心的骨架,完整代码量不大,核心逻辑分散在三个方法里:run()负责状态流转,_plan_and_reason()负责非结构化推理,_struct()负责结构化与重试。

class StructuredAgent: def __init__(self, backend: LLMBackend, temperature: float = 0.2): self.backend = backend self.temperature = temperature def run(self, task: str, text: str, schema_type: Type[BaseModel], system_prompt: str | None = None) -> BaseModel: state = AgentState.INIT plan_result = "" reasoning = "" raw_output = "" retries = 0 # Init schema_json = json.dumps(schema_type.model_json_schema(), ensure_ascii=False) final_system_prompt = system_prompt or "你是一名结构化数据抽取助手。" # Plan + Reason:合成一次请求,先理解,再分析 state = AgentState.PLAN plan_and_reason_prompt = self._build_plan_reason_prompt(task, text) reasoning = self.backend.complete( [ {"role": "system", "content": final_system_prompt}, {"role": "user", "content": plan_and_reason_prompt}, ], temperature=self.temperature, ) # Struct:基于分析结果生成 JSON state = AgentState.STRUCT while retries <= self.max_retries: struct_prompt = self._build_struct_prompt(schema_json, reasoning, retries) raw_output = self.backend.complete( [ {"role": "system", "content": final_system_prompt}, {"role": "user", "content": struct_prompt}, ], temperature=self.temperature, ) result, error = self._validate(raw_output, schema_type) if result is not None: return result retries += 1 if retries <= self.max_retries: reasoning += f"\n[系统提示] 上次结构化输出错误:{error}" raise RuntimeError(f"重试{self.max_retries}次后仍然无法通过校验,原始输出: {raw_output}")

这个流程里我把 Plan 和 Reason 合成了一次模型调用。原因是实测发现这两步分开调的效果,和合在一起调的效果差别不大,但分开会多消耗一倍时间和 token。我做了一个简化:规划信息点在 prompt 里提一句"先列出关键信息点,再给出分析",既保留了中间思考空间,又省了一次往返。

5.2 三个实际任务效果对比

跑通了核心代码后,我用三个真实任务做了验证。第一是合同要素抽取,输入 3000 字的采购合同,schema 包含双方主体、金额、付款条件、违约责任,Pydantic 校验一次通过率约 95%。第二是用户反馈分类,输入 500 条电商客服记录,schema 要求输出 sentiment、category、urgency 三个字段,通过率约 98%。第三是技术文档转 FAQ,输入一篇 5000 字的技术文档,schema 要求输出 5 到 10 组问答对,通过率约 88%,问题主要集中在"问答对数量没达到下限"和"答案太长导致截断"。

任务输入规模Schema 复杂度一次校验通过率平均耗时
合同要素抽取3000字中等(6字段)95%6.8秒
用户反馈分类200字/条简单(3字段)98%2.1秒
技术文档转FAQ5000字较高(10组嵌套)88%11.5秒

以上耗时基于 7B 量化模型本地部署测得。可以看到,字段越多、嵌套越深,通过率越低,这也是第 6 章要讲踩坑的起点。

5.3 与之前 Tool Calling 方案的对比

同一个合同抽取任务,我之前用 Tool Calling 实现时,函数定义要写 40 行 JSON Schema,还要处理 parameter 解析、校验、错误回传。现在的无工具版本,核心只是两个 Prompt 模板加一个 Pydantic 模型。在正确率上,两者其实很接近,但无工具版本在本地量化模型上的稳定性明显更好,因为省掉了最不稳定的 function call 格式生成环节。

另外一个对比维度是调试成本。Tool Calling 方案报错时,你得同时查"模型是否调用了函数""参数是否合法""返回值是否被正确解析"三个环节。无工具方案报错时,只可能出现在"JSON 解析或 schema 校验",问题定位一目了然。

6. 踩坑记录:结构化 Agent 最容易翻车的四个细节

6.1 模型输出 JSON 里多出来的 markdown 包装

第一个踩到的坑非常典型:模型输出的内容是带有 markdown 代码块包装的——比如输出```json ... ```。这种做法在对话场景里很友好,但在程序化解析时会导致json.loads直接失败。

处理方法分两层。第一层是在 prompt 里写死"只输出 JSON 本身,不要使用任何 markdown 标记";第二层是在解析时做兜底,用正则提取字符串中第一对{...}的内容。不要只依赖其中一层,因为总有一些模型哪怕你反复强调,还是会画蛇添足。

import re def _extract_json(raw: str) -> str: try: return json.loads(raw) except json.JSONDecodeError: match = re.search(r"\{.*\}", raw, re.DOTALL) if match: return match.group(0) raise

6.2 Pydantic 校验失败时,重试信息必须具体到字段

重试机制刚开始做的时候,我只告诉模型"格式不对,请重试"。结果效果很差,模型反复输出同一份错误 JSON。后来我养成了一个习惯:无论 JSON 解析错误还是字段校验错误,都把错误信息原样拼进重试 prompt。

比如 Pydantic 报"amount字段类型错误,期望 float,实际收到字符串",模型看到后就会拿掉单位、去掉逗号分隔符。再比如 JSON 里根本没有payment_terms字段,模型看到错误后就会把遗漏字段补上。重试不是让模型猜,而是让模型基于具体错误做修正。

6.3 中文编码与前后端字符集问题

这个坑很隐蔽,但影响极大。一次在 Windows 服务器上跑服务时,输出到控制台的中文一直是乱码,但写入数据库又正常。排查了半天才发现问题出在控制台默认的 GBK 编码,和 Python 的 UTF-8 输出不一致。后来我把所有日志写入文件而非控制台,并在代码入口强制设置了PYTHONIOENCODING=utf-8才算彻底解决。

在做 Agent 项目时,只要你处理的是中文数据,就一定要把字符集问题当成一等公民来对待。建议所有 I/O 边界——读文件、写库、打印日志——都显式指定encoding="utf-8",不要依赖系统默认。

6.4 长文本截断对结构化输出的隐性影响

技术文档转 FAQ 这个任务,通过率只有 88%,很大一部分原因就是长文本截断。输入 5000 字,上下文窗口又是 4096 的话,文本会被硬截断,合同后半段的违约责任条款、付款条款全丢了,schema 里相关字段自然填不上。

后来我调整了策略:先按章节做文本分块,每块单独跑一次结构化,最后在代码里做结果合并。比如合同有 8 个章节,每个章节跑一次抽取,再把结果拼接成一份完整合同对象。这样做之后,通过率从 88% 提到了 96%。代价是多几次模型调用,但对于"一次失败就要人工处理"的场景,这点开销完全值得。

7. 从"能用"到"好用":无工具 Agent 的扩展空间

7.1 给 Agent 增加记忆与上下文窗口管理

目前的实现是无状态的——每次调用run()都是全新开始。在很多场景里这样够用,但如果你希望 Agent 记住上次任务的处理风格,或者在同一批任务里复用中间推理结果,就需要引入记忆模块。

一个低成本做法是在 Agent 内部加一个memory字典,按任务类型缓存最近一次的成功 prompt 和输出示例,下次同类任务直接把这些内容作为 few-shot 添进 prompt。我试过用这个方式处理同类格式的重复任务,第二次开始的通过率能提升好几个百分点。

7.2 多 Agent 协作:无工具 Agent 作为子任务执行器

这类结构化 Agent 还有一个很自然的定位:作为多 Agent 协作架构里的子任务执行器。主 Agent 负责理解任务、拆分任务,把每个子任务分给一个独立的StructuredAgent实例,然后汇总所有结果。由于这个 Agent 不依赖工具就可以稳定输出结构化数据,在多 Agent 编排里属于"可靠的工人"。

而且,因为它和模型后端解耦,不同子任务完全可以用不同的模型后端。比如简单分类给 7B 小模型跑,复杂合同抽取出给 72B 模型跑,成本和质量都能兼顾。

7.3 从结构化到半结构化:Agent 输出不止于 JSON

最后分享一个我正在做的扩展:把输出从"严格 JSON"拓展到"JSON + 文本摘要"的混合结构。比如合同抽取不仅要输出结构化字段,还要生成一段 100 字左右的合同风险提示。我只需要在 Pydantic 模型里加一个summary字段,Struct 阶段自然就会多生成一段文本。这看起来是个小改动,实际上让 Agent 的使用场景一下子宽了很多——它不再只能做抽取和分类,连"抽取 + 写作"这种复合任务也能一把搞定。

我在实际部署这个 Agent 的过程中最大的体会是:技术选型最重要的是贴合真实场景,Tool Calling 确实很酷,但对"信息处理类"任务来说它是多余的复杂度。把状态机、JSON Schema、运行时校验这三件事做好,一个不调工具的结构化 Agent 就已经能覆盖大多数生产需求了。如果你正在做类似的项目,我建议先从一个 schema 明确的小任务起步,把通过率做到 95% 以上,再考虑增加记忆和多 Agent 协作这些进阶能力。

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

agent-skills 实战:用技能包让 AI coding agent 遵守 TDD 与团队规范

1. 从 agent-skills 说起&#xff1a;为什么它值得单独拿出来聊第一次看到agent-skills这个项目名&#xff0c;我脑子里蹦出来的不是某个具体工具&#xff0c;而是一类正在快速成型的东西——给 AI coding agents 装“技能包”的机制。你可以把它理解成&#xff1a;模型本身是大…

作者头像 李华
网站建设 2026/10/29 6:07:44

从ZIP包到运行:Ollama+Deepseek+Open WebUI本地部署实战

简介&#xff1a;这是Open WebUI官方GitHub项目的ZIP源码包&#xff0c;用于将Ollama本地模型引擎与Deepseek等模型组合成可视化聊天界面。资源面向希望在本地或私有网络部署AI对话平台的开发者和爱好者&#xff0c;解决网页端调用Ollama时交互体验不足、模型切换不便的问题。压…

作者头像 李华
网站建设 2026/10/13 9:38:32

换行符统一神器ponytail:从原理到实战彻底解决CRLF/LF问题

1. 先搞清楚为什么要跟换行符过不去的前几天有个同事跑过来跟我诉苦&#xff0c;说他的Git仓库不知道被谁“下了毒”——某个源代码文件一打开满屏都是红色的修改标记&#xff0c;明明大家只改了几行&#xff0c;diff却把整个文件都标成了已改动。我瞄了一眼就明白了&#xff0…

作者头像 李华
网站建设 2026/10/22 20:04:57

WorkBuddy转DSH:数据转换工具字段映射与时间戳指南

1. 项目概述&#xff1a;这个转换工具到底是干什么的先说明白&#xff0c;WorkBuddy 本身是一款面向个人与团队的任务记录工具&#xff0c;平时大家用它在里面存待办事项、日程安排、项目任务、工时记录这类结构化数据。它的导出格式通常是 JSON 或者 CSV&#xff0c;字段命名也…

作者头像 李华
网站建设 2026/10/22 1:02:45

OpenWorkMate:基于GPT-6与本地模型的私有部署AI工作伙伴实战

1. 从“只会聊天”到“真能干活”&#xff1a;我为什么要做 OpenWorkMate公司里那套 AI 助手&#xff0c;我用了快一年&#xff0c;说实话&#xff0c;前半年新鲜&#xff0c;后半年烦。你问它“上周三的会议纪要里提到的那个供应商报价是多少”&#xff0c;它要么装傻&#xf…

作者头像 李华