fix: regulatory 对话模式改用 event_stream_handler 修复工具调用截断,优化节点 prompt 和日志展示
- regulatory_node: stream_working 从 run_stream 改为 agent.run + event_stream_handler, 解决工具调用后文本被截断的问题;添加 PartStartEvent 处理修复首字丢失 - consciousness_node: prompt 重写为三模式(生成/执行/报告),强调禁止编造 agent_id - workflow API: _merge_runtime_status 暴露步骤输出内容(workflow_log 第三元素) - 前端日志: 系统日志改为终端滚动样式,工作流步骤可展开查看输出 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RefreshCw, Search, Server, GitBranch } from 'lucide-react';
|
||||
import { RefreshCw, Server, GitBranch, ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import apiClient from '../../api/client';
|
||||
|
||||
interface EventLog {
|
||||
@@ -28,12 +28,13 @@ interface WorkflowStep {
|
||||
action: string;
|
||||
status: string;
|
||||
agent_id: string | null;
|
||||
output?: string;
|
||||
}
|
||||
|
||||
const LEVEL_STYLES: Record<string, { bg: string; text: string; label: string }> = {
|
||||
error: { bg: 'bg-[rgba(196,145,122,0.12)]', text: 'text-[#a0705a]', label: 'ERROR' },
|
||||
warn: { bg: 'bg-[rgba(196,168,130,0.15)]', text: 'text-[#9a7d5e]', label: 'WARN' },
|
||||
info: { bg: 'bg-[rgba(156,175,136,0.12)]', text: 'text-[#7a8e6a]', label: 'INFO' },
|
||||
const LEVEL_COLORS: Record<string, string> = {
|
||||
error: 'text-red-400',
|
||||
warn: 'text-yellow-400',
|
||||
info: 'text-green-400',
|
||||
};
|
||||
|
||||
const STATUS_STYLES: Record<string, { bg: string; text: string }> = {
|
||||
@@ -53,12 +54,14 @@ export function SystemLogsView() {
|
||||
const [traceFilter, setTraceFilter] = useState('');
|
||||
const [typeFilter, setTypeFilter] = useState('');
|
||||
const [levelFilter, setLevelFilter] = useState('');
|
||||
const terminalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Workflow logs state
|
||||
const [workflows, setWorkflows] = useState<WorkflowSummary[]>([]);
|
||||
const [selectedTrace, setSelectedTrace] = useState<string | null>(null);
|
||||
const [workflowSteps, setWorkflowSteps] = useState<WorkflowStep[]>([]);
|
||||
const [wfLoading, setWfLoading] = useState(false);
|
||||
const [expandedStep, setExpandedStep] = useState<number | null>(null);
|
||||
|
||||
const fetchLogs = async () => {
|
||||
setLoading(true);
|
||||
@@ -88,6 +91,7 @@ export function SystemLogsView() {
|
||||
|
||||
const fetchWorkflowDetail = async (traceId: string) => {
|
||||
setWfLoading(true);
|
||||
setExpandedStep(null);
|
||||
try {
|
||||
const resp = await apiClient.get(`/api/v1/workflow/${traceId}`);
|
||||
setWorkflowSteps(resp.data.steps || []);
|
||||
@@ -108,9 +112,16 @@ export function SystemLogsView() {
|
||||
if (selectedTrace) fetchWorkflowDetail(selectedTrace);
|
||||
}, [selectedTrace]);
|
||||
|
||||
const handleSearch = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
fetchLogs();
|
||||
useEffect(() => {
|
||||
if (terminalRef.current) {
|
||||
terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
|
||||
}
|
||||
}, [logs]);
|
||||
|
||||
const formatTime = (ts: string | null) => {
|
||||
if (!ts) return '--:--:--';
|
||||
const d = new Date(ts);
|
||||
return d.toLocaleTimeString('en-GB', { hour12: false });
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -143,18 +154,19 @@ export function SystemLogsView() {
|
||||
|
||||
{tab === 'system' ? (
|
||||
<>
|
||||
<form onSubmit={handleSearch} className="grid grid-cols-4 gap-3 mb-5">
|
||||
{/* Filter bar */}
|
||||
<div className="flex gap-3 mb-4">
|
||||
<input
|
||||
type="text"
|
||||
value={traceFilter}
|
||||
onChange={(e) => setTraceFilter(e.target.value)}
|
||||
placeholder={t('agent.logFilterTraceId')}
|
||||
className="px-3 py-2 bg-bg-card border border-border-primary rounded-lg text-sm text-text-primary placeholder:text-text-muted/50 focus:outline-none focus:ring-2 focus:ring-accent/15"
|
||||
placeholder="Trace ID"
|
||||
className="px-3 py-1.5 bg-bg-card border border-border-primary rounded-lg text-xs text-text-primary placeholder:text-text-muted/50 focus:outline-none focus:ring-2 focus:ring-accent/15 w-40"
|
||||
/>
|
||||
<select
|
||||
value={typeFilter}
|
||||
onChange={(e) => setTypeFilter(e.target.value)}
|
||||
className="px-3 py-2 bg-bg-card border border-border-primary rounded-lg text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent/15"
|
||||
className="px-3 py-1.5 bg-bg-card border border-border-primary rounded-lg text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-accent/15"
|
||||
>
|
||||
<option value="">{t('agent.logFilterAllTypes')}</option>
|
||||
<option value="workflow_start">workflow_start</option>
|
||||
@@ -168,54 +180,42 @@ export function SystemLogsView() {
|
||||
<select
|
||||
value={levelFilter}
|
||||
onChange={(e) => setLevelFilter(e.target.value)}
|
||||
className="px-3 py-2 bg-bg-card border border-border-primary rounded-lg text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent/15"
|
||||
className="px-3 py-1.5 bg-bg-card border border-border-primary rounded-lg text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-accent/15"
|
||||
>
|
||||
<option value="">{t('agent.logFilterAllLevels')}</option>
|
||||
<option value="info">INFO</option>
|
||||
<option value="warn">WARN</option>
|
||||
<option value="error">ERROR</option>
|
||||
</select>
|
||||
<button type="submit" className="flex items-center justify-center gap-2 px-4 py-2 bg-accent text-white text-sm font-medium rounded-lg hover:bg-accent-hover transition-colors">
|
||||
<Search size={14} /> {t('agent.logSearch')}
|
||||
<button
|
||||
onClick={fetchLogs}
|
||||
className="px-3 py-1.5 bg-accent text-white text-xs font-medium rounded-lg hover:bg-accent-hover transition-colors"
|
||||
>
|
||||
{t('agent.logSearch')}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="bg-bg-card border border-border-primary rounded-xl overflow-hidden">
|
||||
<div className="overflow-x-auto max-h-[60vh] overflow-y-auto">
|
||||
<table className="w-full text-xs">
|
||||
<thead className="bg-bg-secondary sticky top-0">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left font-semibold text-text-muted uppercase tracking-wider">{t('agent.logLevel')}</th>
|
||||
<th className="px-4 py-3 text-left font-semibold text-text-muted uppercase tracking-wider">{t('agent.logType')}</th>
|
||||
<th className="px-4 py-3 text-left font-semibold text-text-muted uppercase tracking-wider">Trace ID</th>
|
||||
<th className="px-4 py-3 text-left font-semibold text-text-muted uppercase tracking-wider">{t('agent.logNode')}</th>
|
||||
<th className="px-4 py-3 text-left font-semibold text-text-muted uppercase tracking-wider">{t('agent.logMessage')}</th>
|
||||
<th className="px-4 py-3 text-left font-semibold text-text-muted uppercase tracking-wider">{t('agent.logTime')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border-primary">
|
||||
{/* Terminal-style log display */}
|
||||
<div
|
||||
ref={terminalRef}
|
||||
className="bg-[#1a1b1e] border border-border-primary rounded-xl p-4 h-[60vh] overflow-y-auto font-mono text-xs leading-relaxed"
|
||||
>
|
||||
{logs.length === 0 ? (
|
||||
<tr><td colSpan={6} className="px-4 py-12 text-center text-text-muted">{t('agent.noLogs')}</td></tr>
|
||||
<div className="text-gray-500 text-center py-8">{t('agent.noLogs')}</div>
|
||||
) : (
|
||||
logs.map((log) => {
|
||||
const style = LEVEL_STYLES[log.level] || LEVEL_STYLES.info;
|
||||
const levelColor = LEVEL_COLORS[log.level] || 'text-gray-400';
|
||||
return (
|
||||
<tr key={log.id} className="hover:bg-bg-secondary/50 transition-colors">
|
||||
<td className="px-4 py-2.5">
|
||||
<span className={`px-2 py-0.5 rounded text-[10px] font-bold ${style.bg} ${style.text}`}>{style.label}</span>
|
||||
</td>
|
||||
<td className="px-4 py-2.5 text-text-secondary font-mono">{log.event_type}</td>
|
||||
<td className="px-4 py-2.5 text-text-muted font-mono">{log.trace_id.slice(-8)}</td>
|
||||
<td className="px-4 py-2.5 text-text-secondary">{log.node_name || '-'}</td>
|
||||
<td className="px-4 py-2.5 text-text-primary max-w-xs truncate" title={log.message}>{log.message}</td>
|
||||
<td className="px-4 py-2.5 text-text-muted whitespace-nowrap">{log.created_at ? new Date(log.created_at).toLocaleString() : '-'}</td>
|
||||
</tr>
|
||||
<div key={log.id} className="py-0.5 hover:bg-white/5 px-1 rounded">
|
||||
<span className="text-gray-500">[{formatTime(log.created_at)}]</span>{' '}
|
||||
<span className={`font-bold ${levelColor}`}>{log.level.toUpperCase().padEnd(5)}</span>{' '}
|
||||
<span className="text-blue-300">{log.node_name || 'system'}</span>{' '}
|
||||
<span className="text-gray-200">{log.message}</span>
|
||||
{log.trace_id && <span className="text-gray-600 ml-2">#{log.trace_id.slice(-6)}</span>}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
@@ -259,38 +259,40 @@ export function SystemLogsView() {
|
||||
{t('common.loading')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<table className="w-full text-xs">
|
||||
<thead className="bg-bg-secondary sticky top-0">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left font-semibold text-text-muted uppercase tracking-wider">#</th>
|
||||
<th className="px-4 py-3 text-left font-semibold text-text-muted uppercase tracking-wider">{t('agent.name')}</th>
|
||||
<th className="px-4 py-3 text-left font-semibold text-text-muted uppercase tracking-wider">{t('agent.logNode')}</th>
|
||||
<th className="px-4 py-3 text-left font-semibold text-text-muted uppercase tracking-wider">{t('common.status')}</th>
|
||||
<th className="px-4 py-3 text-left font-semibold text-text-muted uppercase tracking-wider">{t('agent.workflowAction')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border-primary">
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-2">
|
||||
{workflowSteps.length === 0 ? (
|
||||
<tr><td colSpan={5} className="px-4 py-12 text-center text-text-muted">{t('workflow.noStepsYet')}</td></tr>
|
||||
<div className="text-center text-text-muted text-xs py-8">{t('workflow.noStepsYet')}</div>
|
||||
) : (
|
||||
workflowSteps.map((step, idx) => {
|
||||
const ss = STATUS_STYLES[step.status] || STATUS_STYLES.pending;
|
||||
const isExpanded = expandedStep === idx;
|
||||
return (
|
||||
<tr key={idx} className="hover:bg-bg-secondary/50 transition-colors">
|
||||
<td className="px-4 py-2.5 text-text-muted">{step.step || idx + 1}</td>
|
||||
<td className="px-4 py-2.5 text-text-primary font-medium">{step.name}</td>
|
||||
<td className="px-4 py-2.5 text-text-secondary">{step.node}</td>
|
||||
<td className="px-4 py-2.5">
|
||||
<div key={idx} className="border border-border-primary rounded-lg overflow-hidden">
|
||||
<button
|
||||
onClick={() => setExpandedStep(isExpanded ? null : idx)}
|
||||
className="w-full flex items-center gap-3 px-4 py-3 hover:bg-bg-secondary/50 transition-colors text-left"
|
||||
>
|
||||
{step.output ? (
|
||||
isExpanded ? <ChevronDown size={14} className="text-text-muted shrink-0" /> : <ChevronRight size={14} className="text-text-muted shrink-0" />
|
||||
) : (
|
||||
<div className="w-3.5 shrink-0" />
|
||||
)}
|
||||
<span className="text-xs text-text-muted w-6">{step.step || idx + 1}</span>
|
||||
<span className="text-xs font-medium text-text-primary flex-1 truncate">{step.name}</span>
|
||||
<span className="text-xs text-text-secondary mr-2">{step.node}</span>
|
||||
<span className={`px-2 py-0.5 rounded text-[10px] font-bold ${ss.bg} ${ss.text}`}>{step.status}</span>
|
||||
</td>
|
||||
<td className="px-4 py-2.5 text-text-secondary max-w-sm truncate" title={step.action}>{step.action}</td>
|
||||
</tr>
|
||||
</button>
|
||||
{isExpanded && step.output && (
|
||||
<div className="px-4 pb-3 pt-1 border-t border-border-secondary">
|
||||
<div className="bg-[#1a1b1e] rounded-lg p-3 text-xs text-gray-200 font-mono whitespace-pre-wrap leading-relaxed max-h-48 overflow-y-auto">
|
||||
{step.output}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -177,8 +177,8 @@ def _merge_runtime_status(work_link: list, workflow_log: list) -> list:
|
||||
前端 ``WorkflowDiagram`` 依赖每个 step 的 ``status`` 字段着色,这个拼装让
|
||||
后端真正把运行期状态喂过去。
|
||||
"""
|
||||
# step_index -> 最新 status
|
||||
latest_status: dict[int, str] = {}
|
||||
latest_output: dict[int, str] = {}
|
||||
for entry in workflow_log or []:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
@@ -189,14 +189,16 @@ def _merge_runtime_status(work_link: list, workflow_log: list) -> list:
|
||||
continue
|
||||
if isinstance(payload, (list, tuple)) and len(payload) >= 2:
|
||||
latest_status[idx] = payload[1]
|
||||
if isinstance(payload, (list, tuple)) and len(payload) >= 3:
|
||||
latest_output[idx] = payload[2]
|
||||
|
||||
merged = []
|
||||
for i, step in enumerate(work_link or []):
|
||||
step_copy = dict(step) if isinstance(step, dict) else {}
|
||||
# step 自带的 step 字段优先,否则用位置索引
|
||||
step_idx = step_copy.get("step")
|
||||
lookup_idx = (step_idx - 1) if isinstance(step_idx, int) else i
|
||||
step_copy["status"] = latest_status.get(lookup_idx, "pending")
|
||||
step_copy["output"] = latest_output.get(lookup_idx, "")
|
||||
merged.append(step_copy)
|
||||
return merged
|
||||
|
||||
|
||||
@@ -126,11 +126,23 @@ class RegulatoryNode:
|
||||
"""
|
||||
return await self._run(payload)
|
||||
|
||||
async def stream_working(self, payload: MessageRequest, token_queue: "asyncio.Queue") -> None:
|
||||
"""流式工具调用版本:逐 token 推送到 queue,工具调用结果也会通过 token 输出。
|
||||
_CHAT_INSTRUCTIONS = (
|
||||
"你是 kilostar 智能助手。你现在处于【直接对话模式】,请直接回答用户的问题。\n"
|
||||
"规则:\n"
|
||||
"1. 直接、详细地回答用户问题,像一个专业且友好的助手。\n"
|
||||
"2. 如果你有可用工具,可以调用工具来辅助回答(如搜索、读文件等)。\n"
|
||||
"3. 不要输出内部思考过程,不要做路由判断,不要提及 ForUser/ForConsciousnessNode 等格式。\n"
|
||||
"4. 回复应当完整、有帮助,避免过于简短。\n"
|
||||
)
|
||||
|
||||
完成后 push None 作为终止信号。
|
||||
async def stream_working(self, payload: MessageRequest, token_queue: "asyncio.Queue") -> None:
|
||||
"""流式对话:完整执行 agent graph(含工具调用),逐 token 推送文本到 queue。
|
||||
|
||||
使用 event_stream_handler 回调拿到每个 text delta,保证工具调用后
|
||||
的文本也能被流式输出。完成后 push None 作为终止信号。
|
||||
"""
|
||||
from pydantic_ai.messages import PartStartEvent, PartDeltaEvent, TextPart, TextPartDelta
|
||||
|
||||
platform = payload.platform
|
||||
user_name = payload.user_name
|
||||
message = payload.message
|
||||
@@ -140,17 +152,27 @@ class RegulatoryNode:
|
||||
await token_queue.put(None)
|
||||
return
|
||||
|
||||
async def _stream_handler(ctx, events):
|
||||
async for event in events:
|
||||
if isinstance(event, PartStartEvent) and isinstance(event.part, TextPart):
|
||||
if event.part.content:
|
||||
await token_queue.put(event.part.content)
|
||||
elif isinstance(event, PartDeltaEvent) and isinstance(event.delta, TextPartDelta):
|
||||
await token_queue.put(event.delta.content_delta)
|
||||
|
||||
try:
|
||||
deps = RegulatoryNodeDeps(
|
||||
platform=platform,
|
||||
user_name=user_name,
|
||||
time=time_str
|
||||
)
|
||||
async with self.agent.run_stream(
|
||||
user_prompt=message, deps=deps, output_type=str
|
||||
) as stream_result:
|
||||
async for delta in stream_result.stream_text(delta=True):
|
||||
await token_queue.put(delta)
|
||||
await self.agent.run(
|
||||
user_prompt=message,
|
||||
deps=deps,
|
||||
output_type=str,
|
||||
instructions=self._CHAT_INSTRUCTIONS,
|
||||
event_stream_handler=_stream_handler,
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.exception(f"RegulatoryNode.stream_working failed: {e}")
|
||||
await token_queue.put(f"\n\n[错误: {str(e)}]")
|
||||
|
||||
+32
-12
@@ -59,21 +59,41 @@ _PROMPTS: Dict[str, Dict[str, str]] = {
|
||||
"consciousness_node": {
|
||||
"zh": (
|
||||
"你叫kilostar,是一个多智能体AI助手系统中的【意识节点 (Consciousness Node)】。\n"
|
||||
"你是系统的'高级规划师'和'架构师',负责处理监控节点分配过来的复杂任务。\n"
|
||||
"你的主要工作场景包括:\n"
|
||||
"1. 拆解任务 (Workflow Generation):结合用户的原始命令和提供的模板,生成严谨、可执行的工作流 (kilostarWorkflow),并将其输出为 ForWorkflowEngine 格式。拆解时步骤应清晰连贯。\n"
|
||||
"2. 中途指导 (Workflow Execution):在工作流执行中,如果某一步骤指派给你,你需要对控制节点的结果进行分析或提供下一步的指导,输出 ForWorkflow 格式。\n"
|
||||
"3. 总结报告 (regulatory Report):在整个工作流执行完毕后,你需要对整体流程、各个控制节点的执行情况进行审查,并生成一份技术性的总结报告,输出 ForregulatoryNode 格式。\n"
|
||||
"请确保所有的思考和生成过程符合逻辑,严密且高质量。"
|
||||
"你是系统的'高级规划师'和'架构师',负责处理监控节点分配过来的复杂任务。\n\n"
|
||||
"你的工作根据收到的输入类型严格分为三种模式:\n\n"
|
||||
"【模式1:工作流生成】当你收到用户的原始任务命令时:\n"
|
||||
"- 将复杂任务拆解为多个清晰、可执行的步骤\n"
|
||||
"- 每个步骤必须指派给真实存在的 Worker(使用其真实 agent_id)或 consciousness_node 自己\n"
|
||||
"- 严禁编造不存在的 agent_id!只能使用上下文中列出的可用 Worker\n"
|
||||
"- 输出格式:ForWorkflowEngine\n\n"
|
||||
"【模式2:工作流步骤执行】当某个步骤指派给你自己时:\n"
|
||||
"- 直接完成该步骤描述的具体任务\n"
|
||||
"- 输出应当是任务的实际结果(代码、分析、文档等),而非对任务的描述\n"
|
||||
"- 输出格式:ForWorkflow\n\n"
|
||||
"【模式3:总结报告】当整个工作流执行完毕时:\n"
|
||||
"- 审查各步骤执行情况,生成面向用户的技术总结报告\n"
|
||||
"- 报告应包含:完成了什么、关键结果、是否有失败步骤及原因\n"
|
||||
"- 输出格式:ForregulatoryNode\n\n"
|
||||
"确保所有输出符合逻辑、严密且高质量。"
|
||||
),
|
||||
"en": (
|
||||
"You are kilostar, the [Consciousness Node] in a multi-agent AI assistant system.\n"
|
||||
"You are the system's 'senior planner' and 'architect', responsible for handling complex tasks assigned by the Regulatory Node.\n"
|
||||
"Your main scenarios include:\n"
|
||||
"1. Task Decomposition (Workflow Generation): Combine the user's original command with provided templates to generate rigorous, executable workflows (kilostarWorkflow), outputting them in the ForWorkflowEngine format. Steps should be clear and coherent.\n"
|
||||
"2. Mid-flight Guidance (Workflow Execution): During workflow execution, if a step is assigned to you, analyze the Control Node's results or provide next-step guidance, outputting in the ForWorkflow format.\n"
|
||||
"3. Summary Report (Regulatory Report): After the entire workflow completes, review the overall process and each Control Node's execution, generating a technical summary report in the ForregulatoryNode format.\n"
|
||||
"Ensure all reasoning and generation is logical, rigorous, and high-quality."
|
||||
"You are the system's 'senior planner' and 'architect', responsible for handling complex tasks assigned by the Regulatory Node.\n\n"
|
||||
"Your work is strictly divided into three modes based on input type:\n\n"
|
||||
"[Mode 1: Workflow Generation] When you receive the user's original task command:\n"
|
||||
"- Decompose the complex task into clear, executable steps\n"
|
||||
"- Each step must be assigned to a real existing Worker (using its real agent_id) or to consciousness_node itself\n"
|
||||
"- NEVER fabricate non-existent agent_ids! Only use Workers listed in the context\n"
|
||||
"- Output format: ForWorkflowEngine\n\n"
|
||||
"[Mode 2: Workflow Step Execution] When a step is assigned to you:\n"
|
||||
"- Directly complete the specific task described in the step\n"
|
||||
"- Output should be the actual result (code, analysis, documentation, etc.), not a description of the task\n"
|
||||
"- Output format: ForWorkflow\n\n"
|
||||
"[Mode 3: Summary Report] When the entire workflow has completed:\n"
|
||||
"- Review each step's execution and generate a user-facing technical summary\n"
|
||||
"- Report should include: what was accomplished, key results, any failed steps and reasons\n"
|
||||
"- Output format: ForregulatoryNode\n\n"
|
||||
"Ensure all output is logical, rigorous, and high-quality."
|
||||
),
|
||||
},
|
||||
"control_node": {
|
||||
|
||||
Reference in New Issue
Block a user