1. Delphi Format 函数遇到 % 就报错,到底发生了什么
如果你在 Delphi 里写过类似Format('select * from t where name like ''%s''', [keyword])的代码,大概率遇到过两种结果:要么编译能过但运行时报EConvertError,要么字符串被莫名其妙截断、参数错位。核心原因只有一个——Format把待格式化字符串里所有的%都当成格式占位符的起始符,而不是普通字符。
Format的签名是function Format(const Format: string; const Args: array of const): string;。它扫描Format参数时,只要碰到%,就会往后读一个格式说明符(%s、%d、%f、%x、%8.2f等)。当你从 Oracle 存储过程、SQL 模板、日志模板里拿到一段本身就含%的文本,比如while MasterCursor%found loop,Delphi 会把%f当成浮点格式符,于是要么找不到对应参数报错,要么把后面的字符吃掉。
这个场景在数据库脚本拼接、动态 SQL 生成、报表模板渲染里非常常见。适合阅读本文的人:正在用 Delphi 7 到 Delphi 12 任意版本、需要把外部文本塞进Format、并且希望顺手把 AI 编码助手的 API 通道也统一管起来的开发者。下面我会先讲清楚转义写法,再给一套可复制的异常捕获配置,最后用 TaoToken 统一 Key 在 Cline 里跑一次从报错到验证通过的完整排查。
2. 先搞懂 Format 的转义规则:%% 才是那个百分号
Delphi 的Format沿用了 C 语言printf家族的约定:要输出一个字面量%,必须写两个%%。这不是可选技巧,是唯一正确的转义方式。
program FormatEscapeDemo; {$APPTYPE CONSOLE} uses System.SysUtils; var SqlTemplate: string; Rendered: string; begin // 错误写法:单个 % 会被当成格式符 try Rendered := Format('while MasterCursor%found loop', []); Writeln('错误写法结果: ', Rendered); except on E: EConvertError do Writeln('错误写法抛出异常: ', E.ClassName, ' - ', E.Message); end; // 正确写法:%% 转义为字面量 % Rendered := Format('while MasterCursor%%found loop', []); Writeln('正确写法结果: ', Rendered); // 混合场景:既有字面量 % 又有真实占位符 SqlTemplate := 'select * from t where name like ''%%%s%%'' and status = %d'; Rendered := Format(SqlTemplate, ['Tom', 1]); Writeln('混合场景结果: ', Rendered); Readln; end.运行结果会是这样:
错误写法抛出异常: EConvertError - Format '%f' invalid or incompatible with argument 正确写法结果: while MasterCursor%found loop 混合场景结果: select * from t where name like '%Tom%' and status = 1注意混合场景那行:%%%s%%拆开看是%%+%s+%%,最终渲染成%Tom%。这是拼接 LIKE 模糊查询时最常用的写法,很多人第一次写会漏掉一个%,导致 SQL 语法错误。
注意:
%%只在Format的第一个参数里生效。如果你把含%的字符串放在Args数组里,它不会被解析,原样输出。所以问题永远出在模板串本身。
2.1 什么时候不该用 Format
如果你的字符串里%数量远多于真实占位符,逐个改成%%既费眼又容易漏。这时候有两个替代方案:
一是用StringReplace先做占位符替换,再交给Format:
function SafeFormat(const Template: string; const Args: array of const): string; var Escaped: string; begin // 先把所有 % 转义,再把自定义占位符还原成 %s Escaped := StringReplace(Template, '%', '%%', [rfReplaceAll]); Escaped := StringReplace(Escaped, '{0}', '%s', [rfReplaceAll]); Result := Format(Escaped, Args); end;二是干脆放弃Format,用TStringBuilder或string.Format之外的模板引擎。但对于存量代码,%%仍是最小改动方案。
3. TaoToken 前置:把 AI 编码通道的 Key 统一管起来
排查这类问题时,我经常需要让 AI 助手帮我读一段 Delphi 代码、解释报错、生成转义后的模板。如果每个工具都单独配一套 Key,切换起来很乱。TaoToken 提供统一的 API 通道,一个 Key 可以同时给 Cline、模型对话、Coding Plan 等场景用。
它的官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api 。你需要先在控制台创建一个 API Key,然后把它填进 Cline 的配置里。
具体操作路径:
- 打开控制台创建 Key:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
- 查看 Key 列表:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite
- 接入文档参考:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
拿到 Key 之后,Cline 的配置走的是settings.json骨架。下面这段可以直接复制,把your-api-key-here换成你自己的 Key。
{ "cline.apiProvider": "openai", "cline.openAiApiKey": "your-api-key-here", "cline.openAiBaseUrl": "https://taotoken.net/api", "cline.openAiModelId": "claude-sonnet-4-20250514", "cline.customInstructions": "回答 Delphi 问题时优先给出可编译的 Pascal 代码,涉及 Format 函数时提醒 %% 转义。" }提示:
openAiBaseUrl结尾不要带/v1,TaoToken 的 API 路径已经处理好版本前缀。如果你填成https://taotoken.net/api/v1,请求会 404。
如果你更习惯用 Claude Code 那套命令行工作流,可以看 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=ClaudeCodeAnthropic&utm_campaign=rewrite ,配置逻辑类似,只是入口不同。
4. 可复制配置:异常捕获 + 转义工具函数
光知道%%还不够,生产代码里必须能捕获格式异常并给出可读信息。下面这个单元把转义和捕获封装在一起,可以直接放进你的项目。
unit uFormatSafe; interface uses System.SysUtils; type EFormatSafeError = class(Exception); /// <summary> /// 安全格式化:自动把模板中的字面量 % 转义,只保留 {n} 形式的占位符。 /// </summary> function SafeFormatEx(const Template: string; const Args: array of const): string; implementation function SafeFormatEx(const Template: string; const Args: array of const): string; var Escaped: string; I: Integer; begin Escaped := Template; // 第一步:把所有 % 转义成 %% Escaped := StringReplace(Escaped, '%', '%%', [rfReplaceAll]); // 第二步:把 {0} {1} ... 还原成 %s %d 等 for I := 0 to High(Args) do Escaped := StringReplace(Escaped, Format('{%d}', [I]), '%s', [rfReplaceAll]); try Result := Format(Escaped, Args); except on E: EConvertError do raise EFormatSafeError.CreateFmt( 'SafeFormatEx 失败,模板=%s,参数个数=%d,原始错误=%s', [Template, Length(Args), E.Message]); end; end; end.调用方式:
uses uFormatSafe; var Sql: string; begin Sql := SafeFormatEx( 'select * from orders where remark like ''%{0}%'' and amount > {1}', ['urgent', 100]); Writeln(Sql); // 输出: select * from orders where remark like '%urgent%' and amount > 100 end.这套写法的好处是:模板里你可以放心写%,不用数到底有几个;真实占位符用{0}、{1}这种更直观的形式;一旦出错,异常信息里带着原始模板和参数个数,排查时不用再猜。
4.1 在 Cline 里让 AI 帮你检查转义
配置好 TaoToken 之后,你可以在 Cline 里直接贴一段报错代码,让它指出哪个%没转义。比如把上面FormatEscapeDemo的错误写法贴进去,问「这段 Delphi 代码为什么抛 EConvertError」,它会定位到%f并给出%%修正。这比手动数百分号快得多。
5. 验证请求:从报错到跑通的完整动作
配置完 Key 和工具函数后,做一次端到端验证。分两步:先验证 TaoToken 通道能通,再验证 Delphi 代码能跑。
第一步,用 curl 验证 API 通道:
curl -X POST https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [ {"role": "user", "content": "Delphi Format 函数里 % 怎么转义?"} ] }'如果返回 JSON 里带choices字段,说明 Key 和通道都正常。如果返回 401,检查 Key 是否复制完整;返回 404,检查 base URL 是否多写了/v1。
第二步,编译运行第 4 节的SafeFormatEx调用示例。预期输出:
select * from orders where remark like '%urgent%' and amount > 100第三步,回到 Cline,把settings.json保存后重启窗口,在对话框里输入「用 Delphi 写一个把 Oracle 存储过程里的 % 转义后传给 Format 的函数」,观察它是否按%%规则生成代码。如果生成正确,说明整条链路——Key、API 通道、模型、本地配置——全部打通。
注意:验证时不要用生产库的真实 SQL 做测试,用上面这种带
%的假模板即可,避免误操作。
6. 本篇常见错排查
报错一:EConvertError: Format '%f' invalid or incompatible with argument
这是最典型的。%f被当成浮点格式符,但你没给浮点参数。解决:把模板里所有非占位符的%改成%%。用第 4 节的SafeFormatEx可以自动处理。
报错二:EConvertError: Format '%s' invalid or incompatible with argument
模板里%s的数量和Args数组长度对不上。常见于%%%s%%被误数成两个占位符。实际它只有一个%s。数占位符时先做一次%%替换再数。
报错三:字符串被截断,%后面的内容消失
比如100%complete渲染成100。因为%c被当成字符格式符,吃掉了后面的complete。解决同上,转义成100%%complete。
报错四:Cline 里请求返回 401
Key 没填对,或者settings.json里字段名写错。检查cline.openAiApiKey是否和 TaoToken 控制台里的一致。如果 Key 刚创建,确认没有多余空格。
报错五:Cline 请求返回 404
openAiBaseUrl写成了https://taotoken.net/api/v1。改成https://taotoken.net/api即可。TaoToken 的路径已经包含版本处理。
报错六:SafeFormatEx里{0}没被替换
检查Args的High值。如果传了 2 个参数但模板里写了{2},循环不会覆盖到。占位符编号必须从 0 连续到Length(Args)-1。
报错七:Delphi 7 下StringReplace的rfReplaceAll不生效
老版本需要uses StrUtils,且确认TReplaceFlags类型可用。如果还是不行,手动写循环替换。
7. 把通道和转义一起收进工具箱
Delphi 的Format转义本身不复杂,%%就是全部答案。真正花时间的是:存量代码里%散落各处、报错信息不指向具体位置、以及排查时还要在多个 AI 工具之间切换 Key。把SafeFormatEx这种封装沉淀下来,再把 AI 编码通道统一到 TaoToken 一个 Key 上,下次遇到%报错,你只需要贴代码、看定位、改转义三步。
如果你还想验证不同模型对 Delphi 代码的理解差异,可以直接在模型对话里切换:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。长期做编码和 Agent 任务的话,Coding Plan 更适合:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。接入细节都在文档里:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。