在 Process Simulate (PS 2402) 内嵌一个 AI 智能体,通过工具调用查询/操作场景。 原生 C# 进程内方案:直连 DeepSeek (OpenAI 兼容) 的 tool-calling 循环,所有工具执行都落在 PS 的 UI 主线程上, 不依赖 Node、不跨进程,最稳。
v3 里程碑:UI 全 HTML 化(WebView2 承载)、记忆系统三层(片段/事实/踩坑)、文件上传解析(xlsx/csv/txt)、审批模式三档可切换。
TxAgent/
TxAgentCommand.cs 插件入口 (TxButtonCommand),注册工具、开窗
Core/ 与 PS 解耦的 agent 核心 (可独立编译/单测)
LlmModels.cs DeepSeek/OpenAI 的请求/响应 DTO (messages, tools, tool_calls, usage)
DeepSeekClient.cs 直连 /chat/completions 的 HTTP 客户端 (Bearer + TLS1.2 + SSE 流式)
KeyStore.cs API key 的 DPAPI 加密落盘 (插件文件夹优先)
PsContext.cs 把 PS 调用同步路由回主线程 (SynchronizationContext.Send)
ITxAgentTool.cs 工具接口 + 可选基类
ToolRegistry.cs 工具注册表 (v3 加 Tools 只读枚举供 UI 展示)
AgentLoop.cs 编排循环 + 静态 Current 引用 + 经验萃取入口
AuditLog.cs 变更类工具的审批/执行结果追加日志
Recipe.cs 配方数据模型 (步骤 + 参数模板)
RecipeStore.cs 配方持久化 (recipes.json)
RecipeTool.cs 配方 → 工具的包装 (参数替换 + 只读继承 + 递归保护)
ConversationStore.cs 多对话持久化 (conversations/{id}.json)
── 记忆系统 (v2) ──
SnippetStore.cs 代码片段库 (snippets.json,自动存/按需注入)
FactsStore.cs 跨对话事实/偏好 (facts.json,Jaccard 去重)
GotchasStore.cs 踩坑清单 (gotchas.json,CS1061/CS0117 精准签名)
LessonExtractor.cs 对话末经验萃取 (独立 LLM 调用产结构化 JSON)
TaskPlan.cs per-conversation 任务清单 (update_plan 工具)
── 文件上传 (v3) ──
UploadStore.cs 内存字典 + %TEMP%\TxTools.Agent\uploads\{convId}\
XlsxReader.cs xlsx 读取 (基于 DocumentFormat.OpenXml)
XlsxWriter.cs xlsx 写出 (手写 Open XML, export_table 用)
FileParserService.cs 按扩展名分发解析 xlsx/csv/tsv/txt/md/json/xml
── LLM 提供商支持 ──
LlmProviders.cs 支持 DeepSeek / OpenAI / 兼容 API 的参数封装
── 用户偏好 ──
UserPrefsStore.cs 用户设置持久化 (userprefs.json)
── 补充工具基础 ──
ToolInputHelpers.cs 工具参数解析助手
MemoryTools.cs 记忆系统的工具实现 (搜索/存储/导出)
UploadTools.cs 文件上传相关工具实现
UI/
TxAgentForm.cs WebView2 壳 (500 行, JS↔C# 消息路由)
chat.html 完整 UI (顶栏/消息/输入/附件/抽屉/modal, 内置简易 Markdown)
ApiKeyDialog.cs 原生 API Key 弹窗 (v3 起 form 不再引用, 保留兜底)
ConversationListDialog.cs 原生历史对话弹窗 (v3 起 form 不再引用, 保留兜底)
CodeApprovalDialog.cs run_csharp 代码审阅框 (v3 仍在用)
Tools/ 具体工具实现 (按功能域分组)
── 场景查询 ──
SceneQueryTool.cs 查询当前文档/选中对象
SceneTreeTools.cs list_children / count_objects 等遍历工具
── 对象查找与选择 ──
BatchTools.cs find_objects(搜索) / batch_rename(批量重命名)
ActionTools.cs select_objects / export_points_excel / export_object_list
── 机器人工具 ──
RobotTools.cs check_robot_base / inspect_robot_kinematics / find_robot_for_op
── 位置与坐标 ──
LocationTools.cs get_object_location / set_object_location / scan_devices_z / align_devices_z
── 碰撞检测 ──
CollisionTools.cs query_collision_sets / 碰撞分析工具
── 仿真控制 ──
SimulationTools.cs simulate_operation (播放/暂停/停止)
── 操作工具 ──
OperationTools.cs list_operations / count_points / list_tcp_options
── API 反射探查 ──
ApiTools.cs list_types / inspect_type / inspect_object
── 代码执行 ──
RunCSharpTool.cs run_csharp (兜底工具,写 C# 代码在 PS 内执行)
── 导出工具 ──
ExportTableTool.cs export_table (导出 Excel)
DocxExportTool.cs export_docx (导出 Word 文档)
── 配方管理 ──
SaveRecipeTool.cs save_recipe (保存配方)
ListRecipesTool.cs list_recipes (列配方)
DeleteRecipeTool.cs delete_recipe (删除配方)
── 代码片段 ──
SnippetTools.cs save_snippet / list_snippets / get_snippet / find_snippet
── CEE/PLC 逻辑控制 ──
PlcTools.cs CEE 内部逻辑工具集:
- get_resource_logic_status(查询资源 LB/SCL 状态)
- list_cee_signals(列信号,区分 CEE 内外)
- list_cee_modules(列 Modules 层级)
- create_cee_signal(创建信号)
- add_logic_to_resource(为资源添加 LB)
- create_scl_container(创建 SCL 容器)
- create_cee_module(创建 CEE 模块)
- create_lb_sensor(创建光传感器)
- list_lb_elements(列 LB 元素)
- connect_signal_to_lb(连接信号到 LB)
- copy_logic(复制逻辑)
── 计划/待办 ──
UpdatePlanTool.cs update_plan (维护多步任务清单)
── 子目录 ──
Ps/ PS SDK 通用桥接 (PsBridge.cs)
Catia/ CATIA 集成工具 (可选)
Docs/ 工具文档生成
libs/ 手工引用的 NuGet 包 (Newtonsoft.Json 等)
- 目标框架:.NET Framework 4.8,C# 7.3
Tecnomatix.Engineering(PS SDK;主窗口继承TxForm)TxTools.Common(FormUiKit;UI 框架与其他插件保持一致)Newtonsoft.Json13.x —— 手工<Reference><HintPath>引libs\目录Microsoft.Web.WebView2(NuGet)—— UI 承载DocumentFormat.OpenXml2.20.0(NuGet)—— xlsx 读取,2.20.0 明确支持 .NET Framework 4.6+System.Net.Http、System.Windows.Forms、System.Drawing、System.Security(DPAPI)
运行前提:
- 网络:PS 工作站需放行到
api.deepseek.com的出站 HTTPS - WebView2 Runtime:Windows 10/11 一般系统级预装;缺失时弹 MessageBox 提示装 Evergreen Runtime
- TLS:客户端已在静态构造里启用 TLS1.2
主窗口就是一个填满的 WebView2 控件,其余全部在 chat.html 里:顶栏(品牌名 + 模型下拉 + 审批模式下拉 + 新对话/历史/工具/萃取/Key 按钮)、消息区、输入框、附件区、抽屉菜单、审批弹窗。
chat.html 部署方式:作为嵌入资源 (Build Action = EmbeddedResource) 打包进 dll,不再依赖 bin 目录物理复制。加载走 WebResourceRequested 拦截 https://chath[...] 虚拟域名。
通信协议 v2:
- JS → C#:
jsReady / setApiKey / switchModel / setApprovalMode / userSend / userStop / newConv / listConvs / openConv / deleteConv / listTools / uploadFile / removeAttachment / extractLessons - C# → JS:
modelList / keyReady / askApiKey / convList / toolList / clear / restore / message / delta / closeAssistant / toolCall / toolResult / status / busy / tokenUsage / attachmentInfo
审批弹窗保留原生:ApprovalRequest 委托签名同步 bool,改成异步会牵连 AgentLoop 深层重构;原生 CodeApprovalDialog / MessageBox 自带消息 pump,不阻塞[...]
关键线程坑:所有对 CoreWebView2 的属性访问(包括 == null 判断)必须在 UI 线程。AgentLoop.SendAsync 在 Task.Run 里跑,事件回调(delta/toolCall/…)[...]
- 首次打开若无已保存的 key,前端弹 modal 输入;点确定后 DPAPI 按当前 Windows 用户加密,保存为插件文件夹下的
deepseek.key(Base64 密文) - 之后每次打开自动解密复用;顶部"Key" 按钮可随时重输覆盖
- 插件目录不可写时(如
Program Files),自动回退到%LOCALAPPDATA%\TxTools.Agent\deepseek.key - 解密绑定当前用户:换 Windows 账户或换机器需重新输入
窗口顶栏可切换(切换后 _loop 用新模型重建,_current.Messages 无损保留继续对话):
deepseek-v4-pro(默认)—— 复杂推理 / agent 工具循环deepseek-v4-flash—— 高并发、低成本;LessonExtractor默认用它,成本低deepseek-chat—— 旧名,兼容用
顶栏第二个下拉,session 级,关窗归位:
| 模式 | 值 | 行为 |
|---|---|---|
| 审批·询问 | ask |
默认。所有变更工具都弹 dialog |
| 审批·半自动 | auto_safe |
run_csharp 仍弹代码审阅框,其他变更工具自动通过 |
| 审批·全自动 | auto_all |
所有变更工具(含 run_csharp)自动通过;切换时前端弹 confirm 二次确认 |
永久白名单(AgentOptions.AutoApproveTools)与模式独立:add_fact / add_gotcha_correction 只写自家 json 库不动 PS 场景,任何模式下都直接通过,AuditLog [...]
AuditLog 完整覆盖:所有变更类工具的每次审批/执行都写一行 audit.log——APPLIED / FAILED / DENIED / AUTO-OK / AUTO-SAFE / AUTO-ALL / APPROVAL-MODE = xxx[...]
AgentLoop.SendAsync 内部 await 网络 I/O 时没用 ConfigureAwait(false),续延回 UI 同步上下文——于是 tool.Execute(...) 天然在 UI 主线程运行,可安全调用[...]
TxAgentForm.HandleUserSendAsync 里 await Task.Run(() => _loop.SendAsync(...)) 把整个 agent 循环丢到线程池,UI 线程保持响应"停止"按钮,PS 界面不冻。工具执行内[...]
固有限制:单个重操作(如遍历所有机器人)在主线程执行时,PS 必然短暂无响应——原生命令也一样。所以系统提示要求 run_csharp 写**有界代[...]
- 请求带
tools({type:"function", function:{name, description, parameters(JSON Schema)}}) - 响应
choices[0].message.tool_calls[],每项含id与function.arguments(JSON 字符串,需JObject.Parse) - 把该 assistant 消息原样追加回去(含 tool_calls),再为每次调用追加一条
role:"tool"消息(带tool_call_id+ 结果文本) - 再次请求,直到模型不再返回 tool_calls
流式:DeepSeekClient.SendStreamAsync 解析 SSE,边收边把文本分片回调 AssistantDelta,并增量累积 tool_calls 分片;前端开一行【助手】→追加分片→工具[...]
ConversationStore 每条对话存成 conversations/{id}.json(含标题/时间/messages)。
- 每轮
HistoryChanged触发SaveCurrent,写回当前对话文件(空对话不落盘) - 开窗时加载最近一条继续;顶部"新对话" = 存好当前 + 开新的;"历史"抽屉可打开/删除任意过往
- 删除当前对话的坑已修:删的若是
_current,立即清空并StartFreshConversation(),否则下次切换时SaveCurrent()会把_current原地写回,出现"删了[...] AgentLoop._messages每轮压缩(保留最近MaxTurnsToKeep=3个 user 回合,其余提炼成摘要),_fullHistory全量保留供持久化和萃取
SnippetStore 把摸索出的可用 run_csharp 代码持久化到 snippets.json。
- AutoSnippet:
run_csharp成功后自动存(带auto_前缀 + 语义标签 +HasSimilarCodeJaccard 去重) - 按需注入 (v2 关键改动):每轮
SendAsync前根据用户消息即时召回 Top-3 相关片段(含完整代码),作为独立 system 消息插入本轮工作记忆,轮[...] find_snippet/get_snippet/save_snippet手动接口保留
FactsStore(facts.json):跨对话保留的用户偏好、场景常量、验证过的 SDK 事实、稳定工作流。
- 类别:
preference/scene_constant/api_fact/workflow/misc - Jaccard≥0.7 自动去重,仅刷新
LastConfirmedUtc BuildSystemPromptWithMemory每轮把 Top-10 注入 system prompt 头部,视为对话默认前提
GotchasStore(gotchas.json):run_csharp 报错的反面教材。
- AutoGotcha:
RunOneTool里run_csharp输出含 CS0xxx/TxNotImplementedException 等特征时自动Record - 精准签名 (v3 关键改动):
ExtractSignature用专用正则识别 CS1061/CS0117/CS0246 的"XX 不包含 YY 的定义"消息,从引号内抠出真正的Type.Member,覆盖 6 [...] - 系统提示注入 Top-15,
{Type.Member → 正解}精确到 API - AI 学到解法后应主动调
add_gotcha_correction补Correction
LessonExtractor(对话末萃取):
- 触发时机:
NewConversation/OpenConversation/OnFormClosed之前,FullHistory.Count >= 4才跑 - fire-and-forget,用便宜模型(
deepseek-v4-flash)独立一次 LLM 调用,产结构化 JSON{facts, gotchas} - 分别落
FactsStore.Add与GotchasStore.AddCorrection
search_past_conversations 工具:跨对话按关键字搜索历史消息,遇到"上次那个方案""我之前是不是处理过 X"时 AI 主动调用。
上传流程:
- 前端 drag-drop / paste / 点击附件按钮 →
FileReader.readAsDataURL→ base64 postMessage 给 C# UploadStore.Store(convId, filename, bytes)落到%TEMP%\TxTools.Agent\uploads\{convId}\FileParserService.Parse按扩展名分发解析,产 500-2000 字符摘要- 摘要通过
attachmentInfo回传前端;前端在附件卡片里显示可折叠展开 - 用户发送时,C# 把附件摘要拼到用户消息前缀,AI 直接看到即可判断能否答
支持格式:.xlsx / .csv / .tsv / .txt / .md / .log / .json / .xml。
- xlsx:基于 DocumentFormat.OpenXml SDK(不再是自己手写的 XmlReader),正确处理 SharedString / InlineString / Boolean / 命名空间前缀(如
x:sheet)等所有边[...] - csv/tsv:自动检测分隔符(
, ; \t频次统计),支持 quoted "..." + 双引号转义 - 文本类:UTF-8 优先,失败回退 GBK
按需精读:read_uploaded_file(file_id, sheet?, row_from?, row_to?, char_from?, char_to?) 让 AI 分片读大文件,单次上限 12000 字符,防止塞爆 token。
清理:切对话不清(用户可能切回来引用同一附件),关窗时 UploadStore.ClearAll() 统一清理临时目录。
update_plan(items) 让 agent 把复杂多步任务拆成带状态的清单(整表替换,每项 text+done)。
- v2 修复:
TaskPlan改为 per-conversation(原全局静态单例会跨对话污染),AgentLoop.SetConvId时同步TaskPlan.SetActiveConversation(convId) - 系统提示要求:多步任务前先列计划、每完成一步更新状态
现成工具覆盖不到时,agent 可以"从内部读懂 API、再据此写代码":
-
API 探查(只读):
list_types(keyword)在已加载程序集(优先 Tecnomatix)搜类型inspect_type(type_name)反射列出某类型的公共属性/方法/事件签名inspect_object(name?)探查活动对象的运行时类型与各属性取值
-
自写代码:
run_csharp(code)用 .NET 自带 CodeDom 在 PS 进程内编译执行。代码作为方法体注入(已using Tecnomatix.Engineering,可用 `TxApplication.ActiveD[...]安全门控:
run_csharp是变更工具,ask/auto_safe模式下每次强制审批,CodeApprovalDialog展示完整代码供审阅- 执行包在 Undo 块里(可 Ctrl+Z 撤销)
- 审批/结果写入
audit.log;编译/运行异常都回传(AI 借此自我修正) - 失败自动
GotchasStore.Record;成功自动SnippetStore.Upsert - C# 5 语法(无字符串插值 /
?./ 表达式体);编译出的程序集无法卸载,宜偶发使用
典型链路:
list_types("Collision")→inspect_type("TxCollisionRoot")→run_csharp(...)完成一个没有现成工具的操作。
配方 = 一串对现有工具的调用 + {{参数}} 模板。能力完全被原子工具集合框死,无编译器、无新依赖,也不会引入超出现有工具的破坏面。
工作流:
- agent 用现有工具跑通一个多步任务
save_recipe(name, description, parameters, steps),steps[].tool必须是已存在的工具名,steps[].input模板里用{{参数名}}引用parameters- 保存校验步骤工具都存在 → 写入
recipes.json→ 即时注册成新工具 - 启动时
RecipeStore.Load()把已保存配方注册回来;list_recipes查、delete_recipe(name)删
安全:IsReadOnly 按步骤继承——全只读免审批;任一步会改场景则整条配方执行前一次性确认,内部步骤不二次弹窗。save_recipe 本身只读,但[...]
局限(有意):只支持"参数化的固定序列",无分支/循环/条件——那类逻辑由 agent 在对话里直接编排工具。
预置配方(SeedDefaultRecipes,只入内存不写盘):
preflight_check列操作 + 焊点数 + 参考系scene_overview类型直方图 + 当前文档robot_auditBASE0 校验 + 类型统计weld_preflight焊接前置检查
用户同名配方优先,不覆盖。
场景查询:
query_scene—— 查询当前文档/选中对象基本信息count_objects—— 全场景按类型统计/枚举对象list_children—— 展开组件树子对象(如数 CD_L 下设备)list_operations—— 列出选中操作及其属性count_points—— 按类型统计点数(焊点/路径点等)list_tcp_options—— 操作的 TCP 选项check_reachability—— 快速可达性摘要get_reference_frame—— 当前参考坐标系find_objects—— 按名称/类型关键字搜索对象列表
反射探查:
list_types(keyword)—— 在程序集中搜类型inspect_type(type_name)—— 列类型的公共成员inspect_object(name?)—— 探查对象的运行时属性
机器人:
check_robot_base—— BASE0 偏差校验inspect_robot_kinematics—— 运动学信息(关节/TCP)find_robot_for_op—— 查找操作绑定的机器人
位姿:
get_object_location—— 查询对象位置/姿态(XYZ + RPY)scan_devices_z—— 扫描设备 Z 向落地状态
碰撞:
query_collision_sets—— 查碰撞检测组配置
CEE/PLC 逻辑(只读部分):
get_resource_logic_status(name)—— 查资源 LB/SCL 状态list_cee_signals(name_filter?)—— 列信号,区分 CEE 内外list_cee_modules()—— 列 Modules 层级list_lb_elements(resource_name)—— 列 LB 元素(Entry/Exit/Action)
位置:
set_object_location(name, x, y, z, rx, ry, rz)—— 设置对象位置/姿态align_devices_z(names, z_value)—— Z 向对齐设备
命名:
batch_rename(names, mode, old_str, new_str)—— 批量重命名mode:prefix_replace/suffix_replace/regex_replace
仿真:
simulate_operation(name, action)—— 播放/暂停/停止/重置仿真action:play/pause/stop/reset
选中:
select_objects(names)—— 按名称选中对象(替换原选择)
CEE/PLC 逻辑(变更部分):
add_logic_to_resource(name)—— 为资源添加 LogicBehavior(Smart Component)create_scl_container(name)—— 为资源创建 SCL 编程容器create_cee_signal(signal_type, name, address?, data_type?, comment?)—— 创建信号signal_type:input/output/display
create_cee_module(name)—— 创建 CEE 模块(Modules Viewer 层级)create_lb_sensor(resource_name?, sensor_name)—— 在资源上创建光传感器connect_signal_to_lb(resource_name?, signal_name, pin_type, pin_name?)—— 连接信号到 LBpin_type:entry/exit
copy_logic(source_name, target_name)—— 复制逻辑到同类资源
任务计划:
update_plan(items)—— 维护多步待办清单(整表替换)
兜底:
run_csharp(code)—— 写 C# 在 PS 内执行(专属代码审阅框)
export_table(data, filename?)—— 导出 Excel 汇总表export_points_excel(point_type?, use_mfg_name?, folder?)—— 焊点/路径点坐标导出point_type:WeldPoint/PathPoint/ContinuousPoint/All
export_object_list(type_keyword?, folder?)—— 对象清单导出(机器人/设备等)
代码片段:
save_snippet(name, code, description?, tags?)—— 存代码片段list_snippets()—— 列片段库get_snippet(name)—— 取片段代码(+复用计数)find_snippet(keyword)—— 按语义搜片段
事实与偏好:
add_fact(category, content)—— 存事实(自动通过,不改场景)category:preference/scene_constant/api_fact/workflow/misc
list_facts()—— 列事实库
踩坑清单:
add_gotcha_correction(signature, correction)—— 补踩坑正解(自动通过)list_gotchas()—— 列踩坑清单
跨对话搜索:
search_past_conversations(keyword)—— 跨对话按关键字搜历史消息
list_uploaded_files()—— 列当前对话的附件read_uploaded_file(file_id, sheet?, row_from?, row_to?, char_from?, char_to?)—— 精读大文件(分片读,单次 12000 字符上限)
list_recipes()—— 列已保存配方save_recipe(name, description, parameters, steps)—— 保存配方steps[i]:{ "tool": "...", "input": { "param1": "{{param1}}", ... } }
delete_recipe(name)—— 删除配方
- 在
Tools/下实现ITxAgentTool(或继承TxAgentToolBase):Name/Description/IsReadOnly/InputSchema/Execute。工具名限 a-z A-Z 0-9_-,最长 64 - PS 实际调用直接用
Tecnomatix.EngineeringAPI;如需路由到主线程,用PsContext.Current.Run(...) TxAgentCommand.BuildToolRegistry()里reg.Register(new YourTool()),或让自动反射扫描(需公共无参构造)- 需要拿当前 convId 的工具(如 AddFactTool),构造函数注入
() => AgentLoop.Current?.CurrentConvId
TxAgentCommand 的 AutoRegisterTools 方法会自动扫描程序集中所有实现 ITxAgentTool 且有公共无参构造的类,自动补注册。
- 新加工具只要有默认构造函数,扔进
Tools/文件夹即可生效 - 需依赖注入的工具(如
SaveRecipeTool(reg)/AddFactTool(convIdSupplier)/RecipeTool(recipe,reg))继续手工注册,因无参构造会被自动扫描自然忽略 - 同名已注册工具会被跳过,上面手工注册的优先级更高
| 路径 | 内容 |
|---|---|
{插件目录}\deepseek.key |
DPAPI 加密的 API Key |
{插件目录}\conversations\{id}.json |
每条对话 |
{插件目录}\recipes.json |
已保存配方 |
{插件目录}\snippets.json |
代码片段库 |
{插件目录}\facts.json |
跨对话事实/偏好 |
{插件目录}\gotchas.json |
踩坑清单(含正解) |
{插件目录}\audit.log |
变更操作审计日志 |
{插件目录}\userprefs.json |
用户设置(模型、审批模式等) |
%TEMP%\TxTools.Agent\uploads\{convId}\ |
上传附件临时目录(关窗清理) |
插件目录不可写时(如部署在 Program Files),自动回退到 %LOCALAPPDATA%\TxTools.Agent\。
- F12 打开 DevTools:Console 看 JS 错误、Sources 看 chat.html 真实行号、Network 看拦截请求
- VS 输出窗口 会打印
[TxAgent] chat.html loaded, length=..., startsWith=..., endsWith=...和[XlsxReader] sharedStrings loaded / 找到 N 个 sheet等诊断 - 前端 JS 崩了会通过
window.onerror显示在状态栏(不再静默"初始化中") - 前端 3 秒未收到 C# 的
modelList会在页面顶部弹红色横幅提示按 F12 - 审计文件
audit.log记录所有变更操作,追溯出问题的时机与工具名
run_csharp用 C# 5 语法(自带 CodeDom 编译器):无字符串插值$"..."、无?.、无表达式体、var x = null不合法run_csharp编译出的程序集不能卸载,宜偶发使用;不建议高频调用- 文件上传上限 20 MB(JS 端硬阻),更大文件通过
read_uploaded_file分片读 - WebView2 CoreWebView2 严格 UI 线程 affinity:所有属性访问必须在 UI 线程,别的线程读
== null都会抛