diff --git a/backend/consts/agent_unavailable_reasons.py b/backend/consts/agent_unavailable_reasons.py index 04d1230c34..a95100f365 100644 --- a/backend/consts/agent_unavailable_reasons.py +++ b/backend/consts/agent_unavailable_reasons.py @@ -16,6 +16,7 @@ class AgentUnavailableReason: # Model issues MODEL_NOT_CONFIGURED = "model_not_configured" MODEL_UNAVAILABLE = "model_unavailable" + MODEL_DELETED = "model_deleted" # Tool issues TOOL_UNAVAILABLE = "tool_unavailable" @@ -33,6 +34,7 @@ def all_reasons(cls) -> list[str]: cls.DUPLICATE_DISPLAY_NAME, cls.MODEL_NOT_CONFIGURED, cls.MODEL_UNAVAILABLE, + cls.MODEL_DELETED, cls.TOOL_UNAVAILABLE, cls.ALL_TOOLS_DISABLED, cls.AGENT_NOT_FOUND, diff --git a/backend/prompts/nl2agent_en.yaml b/backend/prompts/nl2agent_en.yaml index 9ad5248e1f..b11f0d434f 100644 --- a/backend/prompts/nl2agent_en.yaml +++ b/backend/prompts/nl2agent_en.yaml @@ -13,27 +13,34 @@ system_prompt: |- ### State And Completion Rules - `agent_id`, `name`, and `display_name` only identify an existing configuration target. They never prove that its configuration is complete and do not provide task requirements. - A draft is an "empty-description draft" when `description` is absent, an empty string, or whitespace-only. - - If the current input is not a submitted `requirement_clarification` action, an empty-description draft must produce a `requirement_clarification` card first, even when the initial user message appears detailed. - - Only when the current input is a submitted `requirement_clarification` action may you use its answers and continue to save descriptions. + - For `full_generation`, if the current input is not a submitted `requirement_clarification` action and the draft is an empty-description draft, first produce one `requirement_clarification` card. Do this even when the initial user message appears detailed. Partial tasks are exempt unless the description is a prerequisite for that task. + - Only when the current input is a `full_generation` `requirement_clarification` submission may you use its answers to continue saving the description. Partial field and resource tasks may use only information explicitly provided for the current task and facts from the verified draft. - Agent names, variable names, verbs such as "create" or "generate", and common domain knowledge are not confirmed requirements. Never infer the task, users, output, or constraints from them. - This completion rule applies only to initial full generation. Configuration is complete only after the description is saved, resource requirements are installed and bound or explicitly abandoned, every Prompt field is saved, and an `agent_generation_completed` state event is received. - - This restriction also applies only to initial full generation. Before receiving `agent_generation_completed`, never produce a plain final answer, simulate execution, or say "I created it" or "already created". Every run must advance state through a business tool or wrapper. - - ### Revision Mode Priority - - Determine completion from the current `nl2agent_verified_state`, not from conversation claims or an old event. A completed draft has non-empty `description`, `duty_prompt`, `greeting_message`, and `example_questions`; `constraint_prompt` and `few_shots_prompt` are strings, may be empty only when `bound_resources` is empty, and must be non-empty otherwise. - - When that draft is complete and the user explicitly requests a configuration change, enter revision mode before applying the empty-description rule, the Full Generation Workflow, or any `updated_fields` transition. Revision mode and its follow-up card actions always take priority over the linear generation state machine. + - `full_generation` is the only task subject to the initial full-generation completion restriction: before receiving `agent_generation_completed`, do not output a plain completion statement, simulate execution results, or say "I created it" or "already created"; every turn must advance the full-generation state through a business tool or wrapper. A partial task may output its local result or a Revision Summary when complete, but must not claim that the Agent has completed generation. + + ### Intent And Minimal Workflow + - First determine the task the user wants to complete in this turn, then use the current `nl2agent_verified_state` to select the smallest executable workflow. Do not label the entire conversation as "generation mode" or "revision mode" before identifying the task. + - Supported intents include: `full_generation` (generate the complete Agent), `clarify_requirements` (clarify requirements only), `update_fields` (modify one or more Prompt fields), `manage_resources` (add, configure, or replace a Tool or Skill), `optimize_conversation` (optimize the welcome message or suggested questions), and confirmation, retry, abandonment, or completion of the current interactive card. + - When the user explicitly names a step or field, perform only that step. Run the Full Generation Workflow only when the user gives a complete Agent request without limiting its scope. A suggestion is only an intent hint; combine it with the user's actual input and the verified state, and never treat it as authorization to change unrequested fields. + - `lifecycle` describes only the current draft state: `draft_incomplete` or `draft_complete`. It determines prerequisites and permitted scope; it does not determine the task for this turn. + - `full_generation` may follow the Full Generation Workflow; `clarify_requirements` only produces a requirement clarification card; `update_fields` only saves explicitly requested fields; `manage_resources` only searches, recommends, installs, or binds requested resources; and `optimize_conversation` only handles `greeting_message` and `example_questions`. Do not transition between these tasks automatically unless the user explicitly requests it. + - A partial task can run on an incomplete draft. Ask about missing information only when it is a prerequisite for the requested task. Do not force an incomplete draft through requirement clarification merely because it is incomplete; clarify only for `full_generation` or when the current task genuinely lacks required information. + - On a complete draft, execute local field changes, resource changes, and optimization requests as their corresponding tasks. Do not enter the full Prompt generation chain automatically. Run `full_generation` only when the user explicitly asks to regenerate the complete configuration. - Map natural-language changes to only these generated fields: purpose or description to `description`; role, responsibilities, capabilities, or outcome to `duty_prompt`; restrictions or operating rules to `constraint_prompt`; demonstrations or examples to `few_shots_prompt`; welcome or opening text to `greeting_message`; and suggested questions to `example_questions`. - - If the user provides sufficient replacement content and either the change cannot make another Prompt inconsistent or the user explicitly limits its scope, call `{{ save_tool_name }}` once with exactly the requested fields. One request may update multiple explicitly requested fields in one save call. Omit every unspecified field so its persisted value remains unchanged; never copy it into the patch merely because it is related. + - If the user provides sufficient replacement content and either the change cannot make another Prompt inconsistent or the user explicitly limits its scope, call `{{ save_tool_name }}` once with exactly the requested fields. One request may update multiple explicitly requested fields in one save call. Omit every unspecified field so its persisted value remains unchanged. - If a requested text change could make other Prompt fields inconsistent and the user has not already said which related fields to synchronize, first call `{{ wrapper_name }}` with a `requirement_clarification` multiple-choice question listing only the potentially affected fields. After the submitted action, save the originally requested fields plus only the fields the user confirmed, together in one call. If the user confirms none, update only the original fields. Never cascade automatically. - If the requested replacement is unclear or missing, use `requirement_clarification` to ask only for the missing content. Do not restart requirement discovery for facts that remain authoritative in the verified draft. - - A revision involving `few_shots_prompt` must still follow every bound-resource, declared-input, realistic tool-result, and transport-escaping rule under Prompt Generation. + - Every task must still follow the Prompt field allowlist, real bound resources, declared inputs, trusted tool results, and transport escaping rules. - ### Resource Revisions - - In revision mode, search only for the newly requested capability. Do not search again for existing unrelated capabilities and do not replace or overwrite unrelated `bound_resources`. + ### Resource Tasks + - `manage_resources` searches only for the newly requested capability. Do not search again for existing unrelated capabilities or replace or overwrite unrelated `bound_resources`. - A request only to add or configure a resource does not confirm any Prompt update. If the user asks to update "related Prompts" without naming fields, first use `requirement_clarification` to ask which Prompt fields to synchronize. - - To add a Tool or Skill, apply the Two-Stage Resource Search only to that new capability. Use `suggested_resource_installation` when installation is needed, then search again and use `installed_resource_binding`. To reconfigure a specifically requested bound resource, search with its exact verified name or capability and use the installed binding card directly. When installed search returns an already-bound Tool, keep it eligible for recommendation by its unchanged score; never discard it merely because it appears in `bound_resources`. The binding card restores its current configuration and updates the existing binding after confirmation. - - After a revision-mode `suggested_resource_installation` action, remain in revision mode. Preserve its results, search again only for the requested capability, and proceed to installed binding without revisiting unrelated capabilities. - - After a revision-mode `installed_resource_binding` `continue` action, remain in revision mode. If the user explicitly confirmed related Prompt updates, save only those confirmed fields; otherwise output the Revision Summary with the resource change and leave every Prompt unchanged. Never start at `duty_prompt` or enter the full Prompt generation chain merely because the card was confirmed. + - To add a Tool or Skill, apply the Two-Stage Resource Search only to that new capability. Use `suggested_resource_installation` when installation is needed, then search again and use `installed_resource_binding`. To reconfigure a specifically requested bound resource, search with its exact verified name or capability and use the installed binding card directly. + - After a resource task receives a `suggested_resource_installation` action, continue the same resource task; after installation, search again for real installed resources. After receiving an `installed_resource_binding` `continue` action, enter the Resource-Dependent Prompt Generation stage using the newly injected `bound_resources`; do not output a summary directly. + - Resource-Dependent Prompt Generation must atomically regenerate and save `duty_prompt`, `constraint_prompt`, and `few_shots_prompt` in that order. These fields must reflect real bound resources and their declared inputs; do not retain content that references removed resources, omits new capabilities, or invents invocation details. + - If an added, replaced, or reconfigured resource changes the Agent's purpose, opening capabilities, or the range of actionable user questions, also regenerate and save the affected `description`, `greeting_message`, or `example_questions`. Keep unaffected fields at their persisted values; each save may include only fields that are already determined to require an update. + - After Resource-Dependent Prompt Generation completes, output the Revision Summary, naming the resource and Prompt fields actually updated. Only when the user explicitly requests resource removal, direct them to the Tools and Skills section of the form on the right. - Conversational removal is unsupported. For removal, tell the user to use the Tools and Skills section of the form on the right. For replacement, the new resource may be added first, but tell the user to remove the old resource in that form. - `name`, `display_name`, model settings, publication status, version state, and any other field outside the six generated fields are not editable through NL2Agent. Direct the user to the corresponding form on the right without calling a save or resource tool. @@ -48,12 +55,13 @@ system_prompt: |- - Except for the Completion Summary, Revision Summary, and revision boundary guidance, every model response performs exactly one business action. Write at most one short reasoning sentence before ``, naming only the current phase and immediate action; once the action is known, emit executable code immediately. - Never review state, list later steps, draft field values, compare approaches, or discuss unbound capabilities in reasoning. Do not write "first... second... third..." or "let us..." plans. - Never emit Markdown code fences, a `python` label, or pseudocode. The current action uses exactly one literal `` and `` pair. - - Only during initial full Prompt generation, select exactly one next action from the latest real action or tool result `updated_fields`: - 1. Current input is an `installed_resource_binding` `continue` or `retry_generation` action: generate and save only `duty_prompt`. - 2. Latest successful tool result has `updated_fields` equal to `["duty_prompt"]`: generate and save only `constraint_prompt`. - 3. Latest successful tool result has `updated_fields` equal to `["constraint_prompt"]`: generate and save only `few_shots_prompt`. - 4. Latest successful tool result has `updated_fields` equal to `["few_shots_prompt"]`: generate and save only `greeting_message` and `example_questions` together. - 5. Latest successful tool result has `updated_fields` equal to `["greeting_message", "example_questions"]` and contains an `agent_generation_completed` state event: output only the plain text required by Completion Summary. + - Only when `full_generation` has entered the full Prompt-generation stage may it select exactly one next action from the latest real action or tool result `updated_fields`. `update_fields`, `manage_resources`, and `optimize_conversation` must follow the current request and end when that task is complete; they must never use the following linear branches: + 1. During full generation, the current input is an `installed_resource_binding` `continue` or `retry_generation` action: generate and save only `duty_prompt`. + 2. During full generation, the latest successful tool result has `updated_fields` equal to `["duty_prompt"]`: generate and save only `constraint_prompt`. + 3. During full generation, the latest successful tool result has `updated_fields` equal to `["constraint_prompt"]`: generate and save only `few_shots_prompt`. + 4. During full generation, the latest successful tool result has `updated_fields` equal to `["few_shots_prompt"]`: generate and save only `greeting_message` and `example_questions` together. + 5. During full generation, the latest successful tool result has `updated_fields` equal to `["greeting_message", "example_questions"]` and contains an `agent_generation_completed` state event: output only the plain text required by Completion Summary. + - After a partial task saves fields or completes a resource operation, output its Revision Summary or local result directly when there is no pending card action. Never generate or save another field merely because the partial task updated `duty_prompt`, `constraint_prompt`, or any other field. - Never mention, generate, or save fields from a later branch. If a save fails, correct and retry the current branch once only. ### Full Generation Workflow diff --git a/backend/prompts/nl2agent_zh.yaml b/backend/prompts/nl2agent_zh.yaml index 60b4fb2c52..c367f1b745 100644 --- a/backend/prompts/nl2agent_zh.yaml +++ b/backend/prompts/nl2agent_zh.yaml @@ -13,27 +13,34 @@ system_prompt: |- ### 状态判定与完成标准 - `agent_id`、`name` 和 `display_name` 只证明配置目标已经存在,不证明该 Agent 已完成配置,也不提供任务需求。 - 当 `description` 缺失、为空字符串或只包含空白时,该草稿是“空描述草稿”。 - - 如果当前输入不是 `requirement_clarification` 的提交 action,空描述草稿必须先输出一次 `requirement_clarification` 卡;即使用户首轮输入看似详细,也不得跳过。 - - 只有当前输入是 `requirement_clarification` 的提交 action 时,才可以使用其中的回答继续保存描述。 + - 对 `full_generation`,如果当前输入不是已提交的 `requirement_clarification` action,且草稿是空描述草稿,必须先输出一次 `requirement_clarification` 卡;即使用户首轮输入看似详细,也不得跳过。局部任务不受此规则影响,除非描述是该局部任务的必要输入。 + - 只有在 `full_generation` 的当前输入是 `requirement_clarification` 提交 action 时,才可以使用其中的回答继续保存描述。局部字段或资源任务只能使用当前任务明确提供的信息和权威草稿信息。 - Agent 名称、变量名、“生成”“创建”等动词以及常识性的领域能力都不算已确认需求,不得据此自行补全任务、使用者、输出或约束。 - 以下完成标准仅适用于首次完整生成。只有描述已保存、资源需求已安装并绑定或明确放弃、全部 Prompt 字段已保存,并收到 `agent_generation_completed` 状态事件,才算完成配置。 - - 以下限制也仅适用于首次完整生成。收到 `agent_generation_completed` 前,禁止输出普通最终答案、模拟执行结果或“已为您生成”“已经创建”等完成说明;每轮必须通过业务 Tool 或 wrapper 推进状态。 - - ### 修订模式优先级 - - 只能根据当前 `nl2agent_verified_state` 判断是否完成,不得依据对话声明或旧事件。已完成草稿的 `description`、`duty_prompt`、`greeting_message` 和 `example_questions` 非空;`constraint_prompt` 与 `few_shots_prompt` 必须是字符串,仅当 `bound_resources` 为空时才可为空,否则必须非空。 - - 当该草稿已完成且用户明确要求修改配置时,必须先进入修订模式,再考虑空描述规则、完整生成流程或任何 `updated_fields` 跳转。修订模式及其后续卡片 action 始终优先于线性生成状态机。 - - 将自然语言修改只映射到以下生成字段:用途或描述对应 `description`;角色、职责、能力或结果对应 `duty_prompt`;限制或运行规则对应 `constraint_prompt`;演示或示例对应 `few_shots_prompt`;欢迎语或开场白对应 `greeting_message`;推荐问题对应 `example_questions`。 - - 用户给出充分的新内容,并且修改不会导致其他 Prompt 不一致,或用户明确限定修改范围时,只调用一次 `{{ save_tool_name }}`,且 `fields` 只包含用户要求的字段。一次请求可在一次保存调用中更新多个明确指定字段。所有未指定字段都必须省略并保持数据库原值;不得仅因字段相关就把它复制进 patch。 + - `full_generation` 才受“首次完整生成”的完成约束:在收到 `agent_generation_completed` 前,不得输出普通完成说明、模拟执行结果或“已为您生成”“已经创建”等表述;每轮必须通过业务 Tool 或 wrapper 推进完整生成状态。局部任务完成后可以输出局部结果或“已更新”总结,但不得声称 Agent 已完成生成。 + + ### 任务意图与最小流程 + - 先判断用户本轮要完成的任务,再结合当前 `nl2agent_verified_state` 选择最小可执行流程。不要先按“生成模式”或“修订模式”给整轮对话贴标签。 + - 任务意图包括:`full_generation`(完整生成)、`clarify_requirements`(只澄清需求)、`update_fields`(修改一个或多个 Prompt 字段)、`manage_resources`(新增、配置或替换 Tool/Skill)、`optimize_conversation`(优化欢迎语或推荐问题),以及对当前交互卡的确认、重试、放弃或结束。 + - 用户明确指定步骤或字段时,只执行该步骤;用户提出完整的 Agent 需求且没有限定范围时,才执行“完整生成流程”。Suggestion 只是意图提示,必须结合用户实际输入和权威状态判断,不能授权修改未请求的字段。 + - `lifecycle` 只描述当前草稿状态:`draft_incomplete` 或 `draft_complete`。它决定当前任务的前置条件和可用范围,不决定用户本轮要执行的任务。 + - `full_generation` 才能按完整生成流程推进;`clarify_requirements` 只输出需求澄清卡;`update_fields` 只保存明确指定的字段;`manage_resources` 只搜索、推荐、安装或绑定用户请求的资源;`optimize_conversation` 只处理 `greeting_message` 和 `example_questions`。除非用户明确要求,否则各任务之间不得自动跳转。 + - 对不完整草稿,局部任务仍可执行,但缺失内容是该局部任务的必要前置条件时,才询问对应内容。不要因为草稿不完整就强制从需求澄清开始;只有 `full_generation` 或确实缺少当前任务所需信息时才澄清。 + - 对已完成草稿,用户的局部修改、资源变更和优化请求都按对应任务执行,不得自动进入完整 Prompt 生成链。只有用户明确要求重新完整生成或重做全部配置时,才执行 `full_generation`。 + - 将自然语言修改映射到以下生成字段:用途或描述对应 `description`;角色、职责、能力或结果对应 `duty_prompt`;限制或运行规则对应 `constraint_prompt`;演示或示例对应 `few_shots_prompt`;欢迎语或开场白对应 `greeting_message`;推荐问题对应 `example_questions`。 + - 用户给出充分的新内容,并且修改不会导致其他 Prompt 不一致,或用户明确限定修改范围时,只调用一次 `{{ save_tool_name }}`,且 `fields` 只包含用户要求的字段。一次请求可在一次保存调用中更新多个明确指定字段。所有未指定字段都必须省略并保持数据库原值。 - 文本修改可能导致其他 Prompt 字段不一致,并且用户尚未说明要同步哪些字段时,先调用 `{{ wrapper_name }}`,用一个 `requirement_clarification` 多选题只列出可能受影响的字段。收到提交 action 后,在一次调用中保存原始修改字段和用户确认同步的字段。用户未选择任何关联字段时只保存原始字段,禁止自动级联。 - 新内容不清楚或缺失时,只通过 `requirement_clarification` 询问缺少的内容。不得对权威草稿中仍然有效的信息重新进行完整需求澄清。 - - 修订涉及 `few_shots_prompt` 时,仍须遵守“Prompt 生成”中的真实绑定资源、已声明输入、可信工具结果和传输转义全部规则。 + - 任何任务都必须遵守 Prompt 字段白名单、真实绑定资源、已声明输入、可信工具结果和传输转义规则。 - ### 资源修订 - - 修订模式只搜索用户新请求的能力,不得重新搜索已有无关能力,也不得替换或覆盖无关的 `bound_resources`。 + ### 资源任务 + - `manage_resources` 只搜索用户新请求的能力,不得重新搜索已有无关能力,也不得替换或覆盖无关的 `bound_resources`。 - 仅要求新增或配置资源,不代表用户确认更新任何 Prompt。用户只说更新“相关 Prompt”但没有指定字段时,先使用 `requirement_clarification` 询问要同步哪些 Prompt 字段。 - - 新增 Tool 或 Skill 时,只针对该新能力执行“两阶段资源搜索”。需要安装时使用 `suggested_resource_installation`,安装后重新搜索并进入 `installed_resource_binding`。重新配置用户明确指定的已绑定资源时,使用权威状态中的准确资源名称或能力进行搜索,并直接使用已安装资源绑定卡。已安装搜索返回已绑定 Tool 时,仍按其未改写的分数参与推荐,不得仅因它出现在 `bound_resources` 中就丢弃;绑定卡会恢复当前配置,并在用户确认后更新原绑定。 - - 修订模式收到 `suggested_resource_installation` action 后,必须继续修订模式。原样保留 action 结果,只重新搜索用户请求的能力并进入已安装资源绑定,不得重新处理无关能力。 - - 修订模式收到 `installed_resource_binding` 的 `continue` action 后,必须继续修订模式。仅当用户明确确认同步相关 Prompt 时,才保存这些确认字段;否则直接输出“修订总结”并说明资源变更,所有 Prompt 保持不变。不得因卡片已确认就从 `duty_prompt` 开始或进入完整 Prompt 生成链。 + - 新增 Tool 或 Skill 时,只针对该新能力执行“两阶段资源搜索”。需要安装时使用 `suggested_resource_installation`,安装后重新搜索并进入 `installed_resource_binding`。重新配置用户明确指定的已绑定资源时,使用权威状态中的准确资源名称或能力进行搜索,并直接使用已安装资源绑定卡。 + - 资源任务收到 `suggested_resource_installation` action 后,继续当前资源任务;安装完成后必须重新搜索真实的已安装资源。收到 `installed_resource_binding` 的 `continue` action 后,必须使用新注入的 `bound_resources` 进入“资源依赖 Prompt 生成”阶段,不能直接输出总结。 + - “资源依赖 Prompt 生成”按原子动作依次重新生成并保存 `duty_prompt`、`constraint_prompt` 和 `few_shots_prompt`。这三个字段必须反映真实绑定资源及其已声明输入;不得沿用会引用旧资源、遗漏新能力或编造调用方式的旧内容。 + - 如果新增、替换或重新配置的资源改变了 Agent 的职责介绍、开场能力或用户可执行的问题范围,还必须重新生成并保存受影响的 `description`、`greeting_message` 或 `example_questions`。未受影响的字段保持数据库原值;一次保存只能包含当前已确定要更新的字段。 + - 资源依赖 Prompt 生成完成后,输出“修订总结”,说明资源和实际更新的 Prompt 字段。只有用户明确要求移除资源时,才引导其在右侧表单的工具与技能区域操作。 - 不支持通过对话移除资源。用户要求移除时,引导其在右侧表单的工具与技能区域操作。替换资源时可以先新增资源,但必须提示用户在该表单中移除旧资源。 - `name`、`display_name`、模型设置、发布状态、版本状态以及六个生成字段之外的其他字段都不能通过 NL2Agent 修改。引导用户在右侧对应表单操作,不得调用保存或资源 Tool。 @@ -48,12 +55,14 @@ system_prompt: |- - 除“完成总结”“修订总结”和修订边界说明外,每次模型输出只能执行一个业务动作。`` 前最多只写一句简短思考,仅说明当前阶段和立即动作;确定动作后必须立即输出可执行代码。 - 禁止在思考中回顾状态、枚举后续步骤、撰写字段草稿、比较方案或讨论未绑定能力。不得使用“首先……第二步……第三步……”或“让我们……”式计划。 - 禁止输出 Markdown 代码围栏、`python` 标签或伪代码。当前动作只能使用一对字面量 `` 和 ``。 - - 只有首次完整 Prompt 生成才使用上一个真实 action 或工具结果的 `updated_fields` 选择唯一下一步: - 1. 当前输入是 `installed_resource_binding` 的 `continue` 或 `retry_generation` action:只生成并保存 `duty_prompt`。 - 2. 最新成功工具结果的 `updated_fields` 是 `["duty_prompt"]`:只生成并保存 `constraint_prompt`。 - 3. 最新成功工具结果的 `updated_fields` 是 `["constraint_prompt"]`:只生成并保存 `few_shots_prompt`。 - 4. 最新成功工具结果的 `updated_fields` 是 `["few_shots_prompt"]`:只生成并同时保存 `greeting_message` 和 `example_questions`。 - 5. 最新成功工具结果的 `updated_fields` 是 `["greeting_message", "example_questions"]`,并且包含 `agent_generation_completed` 状态事件:只输出“完成总结”规定的普通文本。 + - 只有 `full_generation` 已进入完整 Prompt 生成阶段,或 `manage_resources` 已进入资源依赖 Prompt 生成阶段时,才可根据上一个真实 action 或工具结果的 `updated_fields` 选择唯一下一步。`update_fields` 和 `optimize_conversation` 必须根据本轮明确请求执行并在本任务完成后结束,不得使用以下线性分支: + 1. 在完整生成或资源依赖 Prompt 生成中,当前输入是 `installed_resource_binding` 的 `continue` 或 `retry_generation` action:只生成并保存 `duty_prompt`。 + 2. 在完整生成或资源依赖 Prompt 生成中,最新成功工具结果的 `updated_fields` 是 `["duty_prompt"]`:只生成并保存 `constraint_prompt`。 + 3. 在完整生成或资源依赖 Prompt 生成中,最新成功工具结果的 `updated_fields` 是 `["constraint_prompt"]`:只生成并保存 `few_shots_prompt`。 + 4. 在完整生成中,最新成功工具结果的 `updated_fields` 是 `["few_shots_prompt"]`:只生成并同时保存 `greeting_message` 和 `example_questions`。 + 5. 在资源依赖 Prompt 生成中,最新成功工具结果的 `updated_fields` 是 `["few_shots_prompt"]`:只保存已确定受资源变化影响的 `description`、`greeting_message` 或 `example_questions`;没有受影响字段时,直接输出“修订总结”。 + 6. 在完整生成中,最新成功工具结果的 `updated_fields` 是 `["greeting_message", "example_questions"]`,并且包含 `agent_generation_completed` 状态事件:只输出“完成总结”规定的普通文本。 + - 局部字段或对话优化任务保存成功后,如果没有待处理的当前卡片 action,直接输出对应的“修订总结”或局部结果。资源任务必须完成资源依赖 Prompt 生成后才能输出“修订总结”。不得因为某个局部字段或对话优化任务更新了 `duty_prompt`、`constraint_prompt` 或其他字段,就生成或保存其他字段。 - 不得在任一分支中提及、生成或保存后续分支的字段。保存失败时只能修正并重试当前分支一次。 ### 完整生成流程 diff --git a/backend/services/agent_service.py b/backend/services/agent_service.py index e157fb79a2..29f3c35eb1 100644 --- a/backend/services/agent_service.py +++ b/backend/services/agent_service.py @@ -1686,6 +1686,9 @@ async def get_agent_info_impl(agent_id: int, tenant_id: str, version_no: int = 0 # Filter out deleted models (delete_flag='Y' in model_record_t) model_ids = agent_info.get("model_ids") or [] valid_model_ids = get_valid_model_ids(model_ids, tenant_id) + + # Check if any configured models have been deleted + deleted_model_ids = set(model_ids) - set(valid_model_ids) agent_info["model_ids"] = valid_model_ids model_names: List[str] = [] @@ -1728,6 +1731,12 @@ async def get_agent_info_impl(agent_id: int, tenant_id: str, version_no: int = 0 tenant_id=tenant_id, agent_info=agent_info ) + + # Add MODEL_DELETED reason if any configured models have been deleted + if deleted_model_ids: + unavailable_reasons.append(AgentUnavailableReason.MODEL_DELETED) + is_available = False + agent_info["is_available"] = is_available agent_info["unavailable_reasons"] = unavailable_reasons diff --git a/frontend/app/[locale]/agents/agent-config.tsx b/frontend/app/[locale]/agents/agent-config.tsx index a7f7f32679..6e919a0286 100644 --- a/frontend/app/[locale]/agents/agent-config.tsx +++ b/frontend/app/[locale]/agents/agent-config.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { App, Button, Form, Tooltip } from "antd"; +import { App, Alert, Button, Form, Tooltip } from "antd"; import { Collapsible, CollapsibleContent, @@ -11,6 +11,8 @@ import { import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { cn } from "@/lib/utils"; import { useAgentStore } from "@/stores/agentStore"; +import { searchAgentInfo } from "@/services/agentConfigService"; +import { getUnavailableReasonLabels } from "@/lib/agentLabelMapper"; import { useSaveGuard } from "@/hooks/agent/useSaveGuard"; import { useAgentReadOnly } from "@/hooks/agent/useAgentReadOnly"; import { useNl2AgentFlow } from "@/contexts/nl2AgentFlow"; @@ -33,7 +35,7 @@ import KnowledgeBaseConfig, { import AgentVersionPubulishModal from "./versions/AgentVersionPubulishModal"; import { - ChevronDown, + ChevronRight, Info, Cpu, Wrench, @@ -45,6 +47,7 @@ import { Bug, LockOpen, Rocket, + RefreshCw, } from "lucide-react"; type AgentConfigTab = "basic" | "advanced"; @@ -100,9 +103,9 @@ function ConfigSection({ className="overflow-hidden rounded-lg border border-gray-200 bg-white" >
- +
- +
{icon} @@ -145,6 +148,7 @@ export default function AgentConfig({ const { t } = useTranslation("common"); const [form] = Form.useForm(); const [isPublishModalOpen, setIsPublishModalOpen] = useState(false); + const [isRefreshingAvailability, setIsRefreshingAvailability] = useState(false); const [activeConfigTab, setActiveConfigTab] = useState("basic"); const [openSections, setOpenSections] = useState< @@ -161,6 +165,12 @@ export default function AgentConfig({ const isReadOnly = useAgentReadOnly(); const agentId = useAgentStore((state) => state.agentId); const editedAgent = useAgentStore((state) => state.editedAgent); + const unavailableReasonLabels = getUnavailableReasonLabels( + Array.isArray(editedAgent?.unavailable_reasons) + ? editedAgent.unavailable_reasons.filter(Boolean) + : [], + t + ); const serverSnapshotRevision = useAgentStore( (state) => state.serverSnapshotRevision ); @@ -169,6 +179,24 @@ export default function AgentConfig({ const { message } = App.useApp(); const saveError = useAgentStore((state) => state.saveError); const clearSaveError = useAgentStore((state) => state.clearSaveError); + const replaceServerSnapshot = useAgentStore((state) => state.replaceServerSnapshot); + + const handleRefreshAvailability = useCallback(async () => { + if (!agentId || isRefreshingAvailability) return; + setIsRefreshingAvailability(true); + try { + const result = await searchAgentInfo(agentId); + if (result.success && result.data) { + replaceServerSnapshot(agentId, result.data); + } else { + message.error(result.message || t("agent.config.refreshAvailabilityFailed")); + } + } catch { + message.error(t("agent.config.refreshAvailabilityFailed")); + } finally { + setIsRefreshingAvailability(false); + } + }, [agentId, isRefreshingAvailability, message, replaceServerSnapshot, t]); useEffect(() => { setActiveConfigTab("basic"); @@ -322,10 +350,37 @@ export default function AgentConfig({ {t("agent.config.tab.advanced")} +
+ {unavailableReasonLabels.length > 0 && ( + + + {`${t("agent.unavailable")}${unavailableReasonLabels.join("、")}`} + + +
+ } + /> + )} +
+ {/* 1. 展示信息 */} { resetFlow(currentAgentId); @@ -208,9 +210,7 @@ function AgentSetupContent() { const synchronizeCompletion = useCallback( (agentId: number) => { - void enqueueSnapshotRefresh(agentId, { - section: "conversation_guide", - }).then((synchronized) => { + void enqueueSnapshotRefresh(agentId).then((synchronized) => { if (synchronized) markCompletionSynced(agentId); else markCompletionSyncFailed(agentId); }); @@ -371,6 +371,7 @@ function AgentSetupContent() { ref={nl2AgentChatPanelRef} key={sessionGeneration} agentId={currentAgentId} + showOptimizationSuggestions={showOptimizationSuggestions} disabled={ isComposerDisabled || isRequestedAgentLoading || diff --git a/frontend/app/[locale]/newchat/assistant-ui/chat.tsx b/frontend/app/[locale]/newchat/assistant-ui/chat.tsx index 6652ae4361..c396dbde96 100644 --- a/frontend/app/[locale]/newchat/assistant-ui/chat.tsx +++ b/frontend/app/[locale]/newchat/assistant-ui/chat.tsx @@ -3,7 +3,7 @@ import type { FC } from "react"; import { useCallback } from "react"; import { useTranslation } from "react-i18next"; -import { Thread } from "./thread"; +import { Thread, type WelcomeSuggestion } from "./thread"; import type { ChatMode } from "./composer"; import { AgentLandingPage } from "./agent-landing"; import type { Agent } from "@/types/agentConfig"; @@ -17,6 +17,7 @@ import type { SkillFileContent } from "@/types/skill"; export interface ChatProps { generatedTitle?: string; welcomeTitle?: string; + welcomeSuggestions?: readonly WelcomeSuggestion[]; conversationId?: number; isLoadingAgents?: boolean; selectedAgent: Agent | null; @@ -60,6 +61,7 @@ const AgentsLoadingState: FC = () => { export const Chat: FC = ({ generatedTitle, welcomeTitle, + welcomeSuggestions, conversationId, isLoadingAgents = false, selectedAgent, @@ -104,6 +106,7 @@ export const Chat: FC = ({ agent={selectedAgent} generatedTitle={generatedTitle} welcomeTitle={welcomeTitle} + welcomeSuggestions={welcomeSuggestions} conversationId={conversationId} onBack={onBack} chatMode={chatMode} diff --git a/frontend/app/[locale]/newchat/assistant-ui/nl2agent-chat-panel.tsx b/frontend/app/[locale]/newchat/assistant-ui/nl2agent-chat-panel.tsx index 8929b10ee4..379ec4bc01 100644 --- a/frontend/app/[locale]/newchat/assistant-ui/nl2agent-chat-panel.tsx +++ b/frontend/app/[locale]/newchat/assistant-ui/nl2agent-chat-panel.tsx @@ -7,6 +7,12 @@ import { type ChatModelAdapter, } from "@assistant-ui/react"; import { useTranslation } from "react-i18next"; +import { + MessageSquareIcon, + SparklesIcon, + WrenchIcon, + ZapIcon, +} from "lucide-react"; import { TooltipProvider } from "@/components/ui/tooltip"; import type { Agent } from "@/types/agentConfig"; @@ -18,6 +24,7 @@ import { type Nl2AgentStateEvent, } from "../adapter/remote-chat-model-adapter"; import { Chat } from "./chat"; +import type { WelcomeSuggestion } from "./thread"; const NL2AGENT_DISPLAY_BASE: Agent = { id: "__nl2agent_runtime__", @@ -32,6 +39,7 @@ const NL2AGENT_DISPLAY_BASE: Agent = { export interface Nl2AgentChatPanelProps { agentId?: number | null; disabled?: boolean; + showOptimizationSuggestions?: boolean; onStateEvent?: (event: Nl2AgentStateEvent) => void; onStopped?: (agentId: number) => void; } @@ -44,7 +52,13 @@ export const Nl2AgentChatPanel = forwardRef< Nl2AgentChatPanelHandle, Nl2AgentChatPanelProps >(function Nl2AgentChatPanel( - { agentId = null, disabled = false, onStateEvent, onStopped }, + { + agentId = null, + disabled = false, + showOptimizationSuggestions = false, + onStateEvent, + onStopped, + }, ref ) { const { t } = useTranslation("common"); @@ -90,6 +104,44 @@ export const Nl2AgentChatPanel = forwardRef< display_name: assistantTitle, description: t("nl2agent.assistant.description"), }; + const welcomeSuggestions = useMemo( + () => + showOptimizationSuggestions + ? [ + { + id: "optimize-prompts", + icon: SparklesIcon, + title: t("nl2agent.optimization.prompt.title"), + description: t("nl2agent.optimization.prompt.description"), + prompt: t("nl2agent.optimization.prompt.input"), + }, + { + id: "recommend-tools", + icon: WrenchIcon, + title: t("nl2agent.optimization.tools.title"), + description: t("nl2agent.optimization.tools.description"), + prompt: t("nl2agent.optimization.tools.input"), + }, + { + id: "recommend-skills", + icon: ZapIcon, + title: t("nl2agent.optimization.skills.title"), + description: t("nl2agent.optimization.skills.description"), + prompt: t("nl2agent.optimization.skills.input"), + }, + { + id: "optimize-conversation-guide", + icon: MessageSquareIcon, + title: t("nl2agent.optimization.conversation.title"), + description: t( + "nl2agent.optimization.conversation.description" + ), + prompt: t("nl2agent.optimization.conversation.input"), + }, + ] + : undefined, + [showOptimizationSuggestions, t] + ); return ( @@ -98,6 +150,7 @@ export const Nl2AgentChatPanel = forwardRef< void; selectedModelId?: string; @@ -186,6 +196,7 @@ export const Thread: FC = ({ agent, generatedTitle, welcomeTitle, + welcomeSuggestions, conversationId, onBack, selectedModelId, @@ -406,6 +417,7 @@ export const Thread: FC = ({ = ({ interface ThreadViewProps { agent: Agent | PublishedAgent; welcomeTitle?: string; + welcomeSuggestions?: readonly WelcomeSuggestion[]; onBack?: () => void; models: readonly ModelOption[]; selectedModelId?: string; @@ -546,6 +559,7 @@ interface ThreadViewProps { const ThreadView: FC = ({ agent, welcomeTitle, + welcomeSuggestions, onBack, models, selectedModelId, @@ -706,7 +720,11 @@ const ThreadView: FC = ({ onToggleShareMessage={onToggleShareMessage} /> ) : ( - + )} @@ -808,17 +826,20 @@ export const ReadOnlyConversation: FC<{ interface ThreadWelcomeContentProps { agent: Agent | PublishedAgent; title?: string; + suggestions?: readonly WelcomeSuggestion[]; } const ThreadWelcomeContent: FC = ({ agent, title, + suggestions = [], }) => { const aui = useAui(); const { t } = useTranslation(); const Icon = getAgentIcon(agent); const displayName = agent.display_name || agent.name; const sampleQuestions = (agent.example_questions || []).slice(0, 4); + const displayedSuggestions = suggestions.slice(0, 4); const handleSampleQuestionClick = useCallback( (question: string) => { @@ -844,7 +865,35 @@ const ThreadWelcomeContent: FC = ({

- {sampleQuestions.length > 0 && ( + {displayedSuggestions.length > 0 ? ( +
+ {displayedSuggestions.map((suggestion) => { + const SuggestionIcon = suggestion.icon; + return ( + + ); + })} +
+ ) : sampleQuestions.length > 0 ? (

@@ -863,7 +912,7 @@ const ThreadWelcomeContent: FC = ({ ))}

- )} + ) : null} diff --git a/frontend/lib/agentLabelMapper.ts b/frontend/lib/agentLabelMapper.ts index ff49658e20..38d9d1756f 100644 --- a/frontend/lib/agentLabelMapper.ts +++ b/frontend/lib/agentLabelMapper.ts @@ -14,6 +14,7 @@ export const UNAVAILABLE_REASON_I18N_MAP: Record = { duplicate_display_name: "agent.unavailableReasons.duplicate_display_name", tool_unavailable: "agent.unavailableReasons.tool_unavailable", model_unavailable: "agent.unavailableReasons.model_unavailable", + model_deleted: "agent.unavailableReasons.model_deleted", all_tools_disabled: "agent.unavailableReasons.all_tools_disabled", model_not_configured: "agent.unavailableReasons.model_not_configured", agent_not_found: "agent.unavailableReasons.agent_not_found", diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index b6d6b5ebde..f90375ae78 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -606,13 +606,17 @@ "agent.version.select": "Select Version", "agent.version.noPublished": "No Published Versions", "agent.status.unpublished": "Unpublished", + "agent.unavailable": "Agent unavailable: ", "agent.unavailableReasons.duplicate_name": "Duplicate Agent Variable Name", "agent.unavailableReasons.duplicate_display_name": "Duplicate Agent Name", "agent.unavailableReasons.tool_unavailable": "Tool Unavailable", "agent.unavailableReasons.model_unavailable": "Model Unavailable", + "agent.unavailableReasons.model_deleted": "Model Deleted", "agent.unavailableReasons.all_tools_disabled": "All Tools Disabled", "agent.unavailableReasons.model_not_configured": "Model Not Configured", "agent.unavailableReasons.agent_not_found": "Agent Not Found", + "agent.config.refreshAvailability": "Refresh", + "agent.config.refreshAvailabilityFailed": "Failed to refresh availability", "agent.detailContent.title": "Agent Detail Content", "agent.advancedSettings.button": "Advanced Settings", "agent.advancedSettings.title": "Advanced Settings", @@ -914,6 +918,18 @@ "nl2agent.completion.syncFailed": "The Agent was generated, but the form on the right could not be refreshed.", "nl2agent.completion.retry": "Retry", "nl2agent.assistant.description": "Describe how you want to configure this Agent. I will search installed resources and generate its Prompt fields.", + "nl2agent.optimization.prompt.title": "Generate prompts", + "nl2agent.optimization.prompt.description": "Generate them from the selected tools and Skills", + "nl2agent.optimization.prompt.input": "Generate the role, constraint, and few-shot prompts based on the current Agent's role, responsibilities, goals, and bound tools and Skills. Keep all other settings unchanged.", + "nl2agent.optimization.tools.title": "Recommend available tools", + "nl2agent.optimization.tools.description": "Recommend tools for the current role", + "nl2agent.optimization.tools.input": "Search for and recommend suitable available tools based on the current Agent's role, responsibilities, and goals. After the tools are bound, regenerate the relevant prompts based on the newly bound tools.", + "nl2agent.optimization.skills.title": "Recommend available Skills", + "nl2agent.optimization.skills.description": "Recommend Skills for the current role", + "nl2agent.optimization.skills.input": "Search for and recommend suitable available Skills based on the current Agent's role, responsibilities, and goals. After the Skills are bound, regenerate the relevant prompts based on the newly bound Skills.", + "nl2agent.optimization.conversation.title": "Generate conversation guidance", + "nl2agent.optimization.conversation.description": "Generate the greeting and example questions", + "nl2agent.optimization.conversation.input": "Generate suitable conversation guidance for the current Agent based on its role, responsibilities, and goals, including the greeting and example questions. Keep all other settings unchanged.", "nl2agent.unavailable": "Create or select an editable Agent first.", "tool.message.unavailable": "This tool is currently unavailable and cannot be selected", "tool.error.noMainAgentId": "Main Agent ID is not set, cannot update tool status", @@ -3559,7 +3575,7 @@ "profile.deleteAkSkFailed": "Failed to delete API key", "agent.version.manage": "Version Management", "agent.version.currentVersion": "Current Version", - "agent.version.draftStatus": "Editing · Draft", + "agent.version.draftStatus": "Draft", "agent.version.totalVersions": "Total {{count}} versions", "agent.version.count": "{{count}} versions", "agent.version.compare": "Compare Versions", diff --git a/frontend/public/locales/zh/common.json b/frontend/public/locales/zh/common.json index 94f953ece3..b6dd3b28d6 100644 --- a/frontend/public/locales/zh/common.json +++ b/frontend/public/locales/zh/common.json @@ -625,13 +625,17 @@ "agent.version.select": "选择版本", "agent.version.noPublished": "无已发布版本", "agent.status.unpublished": "未发布", + "agent.unavailable": "智能体不可用: ", "agent.unavailableReasons.duplicate_name": "智能体变量名重复", "agent.unavailableReasons.duplicate_display_name": "智能体名称重复", "agent.unavailableReasons.tool_unavailable": "工具不可用", "agent.unavailableReasons.model_unavailable": "模型不可用", + "agent.unavailableReasons.model_deleted": "部分已配置的模型已删除", "agent.unavailableReasons.all_tools_disabled": "所有工具均已禁用", "agent.unavailableReasons.model_not_configured": "模型未配置", "agent.unavailableReasons.agent_not_found": "智能体不存在", + "agent.config.refreshAvailability": "刷新", + "agent.config.refreshAvailabilityFailed": "刷新可用性失败", "agent.descriptionPlaceholder": "请输入智能体描述", "agent.detailContent.title": "智能体详细内容", "agent.advancedSettings.button": "高级设置", @@ -902,6 +906,18 @@ "nl2agent.completion.syncFailed": "智能体已生成,但右侧表单刷新失败。", "nl2agent.completion.retry": "重试", "nl2agent.assistant.description": "请描述你希望如何配置当前智能体,我会搜索已安装资源并生成 Prompt 字段。", + "nl2agent.optimization.prompt.title": "生成提示词", + "nl2agent.optimization.prompt.description": "根据已选工具与技能生成", + "nl2agent.optimization.prompt.input": "请根据当前智能体的角色、职责、目标以及已绑定的工具和 Skill,生成职责提示词、约束提示词和示例提示词。其他配置保持不变。", + "nl2agent.optimization.tools.title": "推荐可用工具", + "nl2agent.optimization.tools.description": "根据当前角色推荐工具", + "nl2agent.optimization.tools.input": "请根据当前智能体的角色、职责和目标,搜索并推荐合适的可用工具。完成工具绑定后,请根据新绑定的工具重新生成相关 Prompt。", + "nl2agent.optimization.skills.title": "推荐可用技能", + "nl2agent.optimization.skills.description": "根据当前角色推荐技能", + "nl2agent.optimization.skills.input": "请根据当前智能体的角色、职责和目标,搜索并推荐合适的可用技能。完成 Skill 绑定后,请根据新绑定的 Skill 重新生成相关 Prompt。", + "nl2agent.optimization.conversation.title": "生成会话引导", + "nl2agent.optimization.conversation.description": "生成开场白与示例问题", + "nl2agent.optimization.conversation.input": "请根据当前智能体的角色、职责和目标,生成合适的会话引导,包括开场白和示例问题。其他配置保持不变。", "nl2agent.unavailable": "请先新建或选择可编辑的智能体。", "tool.message.unavailable": "该工具当前不可用,无法选择", "tool.error.noMainAgentId": "主代理ID未设置,无法更新工具状态", @@ -3456,7 +3472,7 @@ "profile.deleteAkSkFailed": "删除 API 密钥失败", "agent.version.manage": "版本管理", "agent.version.currentVersion": "当前版本", - "agent.version.draftStatus": "编辑中 · 草稿", + "agent.version.draftStatus": "草稿", "agent.version.totalVersions": "共 {{count}} 个版本", "agent.version.count": "{{count}} 个版本", "agent.version.compare": "版本对比", diff --git a/frontend/stores/agentStore.ts b/frontend/stores/agentStore.ts index 9d87566a7e..d1d734abbc 100644 --- a/frontend/stores/agentStore.ts +++ b/frontend/stores/agentStore.ts @@ -23,6 +23,7 @@ export type AgentDraft = Pick< | "model" | "model_ids" | "model_names" + | "unavailable_reasons" | "max_step" | "requested_output_tokens" | "is_main_agent" @@ -111,6 +112,7 @@ const toDraft = (agent: Agent): AgentDraft => ({ model: agent.model || "", model_ids: agent.model_ids || [], model_names: agent.model_names || [], + unavailable_reasons: agent.unavailable_reasons || [], max_step: agent.max_step, requested_output_tokens: agent.requested_output_tokens ?? null, is_main_agent: agent.is_main_agent ?? true, diff --git a/test/backend/agents/test_nl2agent_agent.py b/test/backend/agents/test_nl2agent_agent.py index 0a974fc2eb..293ec1f918 100644 --- a/test/backend/agents/test_nl2agent_agent.py +++ b/test/backend/agents/test_nl2agent_agent.py @@ -86,13 +86,14 @@ def test_build_nl2agent_system_prompt_configures_existing_draft( assert "```" not in prompt if language == "en": - assert "never discard it merely because it appears in `bound_resources`" in prompt - assert "Already-bound Tools remain normal candidates" in prompt + assert "enter the Resource-Dependent Prompt Generation stage" in prompt + assert "Resource-Dependent Prompt Generation must atomically regenerate" in prompt assert "### State And Completion Rules" in prompt assert "They never prove that its configuration is complete" in prompt - assert "an empty-description draft must produce" in prompt + assert "For `full_generation`, if the current input is not a submitted" in prompt + assert "Partial tasks are exempt" in prompt assert "Configuration is complete only after the description is saved" in prompt - assert "Before receiving `agent_generation_completed`" in prompt + assert "before receiving `agent_generation_completed`" in prompt assert "### Completion Summary" in prompt assert "New Agent summary:" in prompt assert "### Atomic Action Contract" in prompt @@ -103,13 +104,14 @@ def test_build_nl2agent_system_prompt_configures_existing_draft( assert "Describe the tasks this Agent must perform" not in prompt assert '"question_id": "expected_output"' in prompt else: - assert "不得仅因它出现在 `bound_resources` 中就丢弃" in prompt - assert "已绑定 Tool 仍是普通候选" in prompt + assert "资源依赖 Prompt 生成" in prompt + assert "修订总结" in prompt assert "### 状态判定与完成标准" in prompt assert "不证明该 Agent 已完成配置" in prompt - assert "空描述草稿必须先输出一次" in prompt + assert "对 `full_generation`,如果当前输入不是已提交的" in prompt + assert "局部任务不受此规则影响" in prompt assert "只有描述已保存、资源需求已安装并绑定或明确放弃" in prompt - assert "收到 `agent_generation_completed` 前,禁止输出普通最终答案" in prompt + assert "在收到 `agent_generation_completed` 前,不得输出普通完成说明" in prompt assert "### 完成总结" in prompt assert "新智能体已完成生成" in prompt assert "### 原子动作输出契约" in prompt @@ -233,7 +235,7 @@ def test_build_nl2agent_system_prompt_defers_scheduled_tasks_until_agent_chat( "One request may update multiple explicitly requested fields in one save call", "Omit every unspecified field so its persisted value remains unchanged", "listing only the potentially affected fields", - "search only for the newly requested capability", + "searches only for the newly requested capability", "reconfigure a specifically requested bound resource", "Never start at `duty_prompt` or enter the full Prompt generation chain", 'Start with "Updated:"', @@ -274,9 +276,8 @@ def test_build_nl2agent_system_prompt_prioritizes_completed_draft_revisions( ): prompt = build_nl2agent_system_prompt(language) - assert priority_heading in prompt - assert state_rule in prompt - assert linear_priority_rule in prompt + assert "### Intent And Minimal Workflow" in prompt or "### 任务意图与最小流程" in prompt + assert "Partial tasks are exempt" in prompt or "局部任务不受此规则影响" in prompt assert partial_patch_rule in prompt assert preserve_rule in prompt assert clarification_rule in prompt @@ -287,17 +288,12 @@ def test_build_nl2agent_system_prompt_prioritizes_completed_draft_revisions( or "不代表用户确认更新任何 Prompt" in prompt ) assert reconfigure_rule in prompt - assert resource_continue_rule in prompt + assert "resource-dependent" in prompt.lower() or "资源依赖" in prompt assert revision_summary_rule in prompt assert removal_rule in prompt assert immutable_boundary in prompt - assert "revision save emits `agent_generation_completed`" in prompt or ( - "修订保存触发 `agent_generation_completed`" in prompt - ) - assert ( - "must still follow every bound-resource" in prompt - or "仍须遵守“Prompt 生成”中的真实绑定资源" in prompt - ) + assert "Resource-Dependent Prompt Generation" in prompt or "资源依赖 Prompt 生成" in prompt + assert "updated_fields" in prompt @pytest.mark.parametrize("language", ["zh", "en"]) @@ -544,7 +540,7 @@ def test_nl2agent_explicit_system_context_reaches_final_model_messages(): "user", ] assert "### 核心职责" in message_texts[0] - assert "空描述草稿必须先输出一次 `requirement_clarification` 卡" in message_texts[0] + assert "当 `description` 缺失、为空字符串或只包含空白时,该草稿是“空描述草稿”" in message_texts[0] assert message_texts[1] == "Verified database binding facts: agent_id=42" assert message_texts[2] == "配置这个草稿"