cockpit v3.1: single JSON response (agent_reply + widgets array), no streaming
Removed stream_response/NDJSON. Returns {success, agent_reply, widgets:[...]}.
Frontend uses response.json() directly, avoiding AgentInput fetch interception.
Agent loop still runs multi-turn tool calling internally.
This commit is contained in:
parent
4effeac396
commit
afce080faa
@ -1,4 +1,4 @@
|
||||
# cockpit_chat.dspy - SDLC Agent Loop v3.1 (streaming NDJSON)
|
||||
# cockpit_chat.dspy - SDLC Agent Loop v3.1
|
||||
import aiohttp, json, os
|
||||
|
||||
action = (params_kw or {}).get('action', 'list_messages')
|
||||
@ -26,7 +26,7 @@ TOOLS_TEXT = json.dumps(TOOLS, ensure_ascii=False)
|
||||
|
||||
def _security_scan(text):
|
||||
t = (text or '').lower()
|
||||
for kw in ('drop table','truncate','rm -rf','密钥','api_key'):
|
||||
for kw in ('drop table','truncate','rm -rf','密钥','api_key'):
|
||||
if kw in t: return True, '危险操作'
|
||||
return False, ''
|
||||
|
||||
@ -95,7 +95,6 @@ def _parse(raw):
|
||||
except: pass
|
||||
return {"action":"reply","message":raw}
|
||||
|
||||
# Widget helpers
|
||||
def _w_text(t): return {"widgettype":"Text","options":{"text":t,"css":"agent-text"}}
|
||||
def _w_card(title, body, kind="success"):
|
||||
colors = {"success":"#10b981","error":"#ef4444","reply":"#6366f1","progress":"#f59e0b"}
|
||||
@ -194,52 +193,49 @@ if action == 'send_message':
|
||||
|
||||
uid = await get_user()
|
||||
org_id = await get_userorgid() or '0'
|
||||
widgets = []
|
||||
|
||||
async def agent_stream():
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
ctx = await _load_ctx(sor, uid)
|
||||
model = await _sel_model(sor, user_model_id)
|
||||
if not model:
|
||||
yield json.dumps({"type":"widget","widget":_w_card("❌ 错误",_w_text("无可用LLM"),"error")},ensure_ascii=False)+'\n'
|
||||
return
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
ctx = await _load_ctx(sor, uid)
|
||||
model = await _sel_model(sor, user_model_id)
|
||||
if not model: return json.dumps({"success":True,"agent_reply":"❌ 没有可用的LLM模型配置"},ensure_ascii=False)
|
||||
|
||||
repos_str = ', '.join([r['n'] for r in ctx['repos']]) or '无'
|
||||
env_text = f"项目: {ctx['pname'] or '未选择'}\n仓库: {repos_str}"
|
||||
system = AGENT_PROMPT.replace('__TOOLS__',TOOLS_TEXT).replace('__ENV__',env_text)
|
||||
msgs = [{"role":"system","content":system}]
|
||||
repos_str = ', '.join([r['n'] for r in ctx['repos']]) or '无'
|
||||
env_text = f"项目: {ctx['pname'] or '未选择'}\n仓库: {repos_str}"
|
||||
system = AGENT_PROMPT.replace('__TOOLS__',TOOLS_TEXT).replace('__ENV__',env_text)
|
||||
msgs = [{"role":"system","content":system}]
|
||||
|
||||
history = await sor.sqlExe("SELECT role,content FROM pipeline_conversations ORDER BY created_at ASC LIMIT 20",{})
|
||||
for h in (history or []):
|
||||
msgs.append({"role":'user' if getattr(h,'role','')=='user' else 'assistant',"content":getattr(h,'content','')})
|
||||
msgs.append({"role":"user","content":message_text})
|
||||
history = await sor.sqlExe("SELECT role,content FROM pipeline_conversations ORDER BY created_at ASC LIMIT 20",{})
|
||||
for h in (history or []):
|
||||
msgs.append({"role":'user' if getattr(h,'role','')=='user' else 'assistant',"content":getattr(h,'content','')})
|
||||
msgs.append({"role":"user","content":message_text})
|
||||
|
||||
await sor.C('pipeline_conversations',{'id':getID(),'role':'user','content':message_text,'msg_type':'text','org_id':org_id,'created_by':uid})
|
||||
await sor.C('pipeline_conversations',{'id':getID(),'role':'user','content':message_text,'msg_type':'text','org_id':org_id,'created_by':uid})
|
||||
|
||||
agent_reply = ''
|
||||
for turn in range(10):
|
||||
raw = await _call_llm(model, msgs, 0.4)
|
||||
act = _parse(raw)
|
||||
if act.get('action') == 'reply':
|
||||
agent_reply = act.get('message', raw); break
|
||||
if act.get('action') == 'tool_call':
|
||||
tool = act.get('tool','')
|
||||
label = _tool_labels.get(tool,tool)
|
||||
yield json.dumps({"type":"widget","widget":_w_progress(f"🔄 {label}")},ensure_ascii=False)+'\n'
|
||||
result = await _exec_tool(sor, tool, act.get('params',{}), ctx, uid, org_id)
|
||||
ok = result.startswith("OK:")
|
||||
yield json.dumps({"type":"widget","widget":_w_card(
|
||||
f"{'✅' if ok else '❌'} {label}", _w_text(result),
|
||||
"success" if ok else "error")},ensure_ascii=False)+'\n'
|
||||
msgs.append({"role":"assistant","content":raw})
|
||||
msgs.append({"role":"user","content":f"工具 {tool} 结果:\n{result}"})
|
||||
continue
|
||||
agent_reply = raw; break
|
||||
agent_reply = ''
|
||||
for turn in range(10):
|
||||
raw = await _call_llm(model, msgs, 0.4)
|
||||
act = _parse(raw)
|
||||
if act.get('action') == 'reply':
|
||||
agent_reply = act.get('message', raw); break
|
||||
if act.get('action') == 'tool_call':
|
||||
tool = act.get('tool','')
|
||||
label = _tool_labels.get(tool,tool)
|
||||
widgets.append(_w_progress(f"🔄 {label}"))
|
||||
result = await _exec_tool(sor, tool, act.get('params',{}), ctx, uid, org_id)
|
||||
ok = result.startswith("OK:")
|
||||
widgets.append(_w_card(f"{'✅' if ok else '❌'} {label}", _w_text(result), "success" if ok else "error"))
|
||||
msgs.append({"role":"assistant","content":raw})
|
||||
msgs.append({"role":"user","content":f"工具 {tool} 结果:\n{result}"})
|
||||
continue
|
||||
agent_reply = raw; break
|
||||
|
||||
if not agent_reply: agent_reply = "处理超时"
|
||||
await sor.C('pipeline_conversations',{'id':getID(),'role':'agent','content':agent_reply,'msg_type':'text','org_id':org_id,'created_by':'system'})
|
||||
yield json.dumps({"type":"widget","widget":_w_card("🤖 驾驶舱 Agent",_w_text(agent_reply),"reply")},ensure_ascii=False)+'\n'
|
||||
if not agent_reply: agent_reply = "处理超时,请简化需求。"
|
||||
widgets.append(_w_card("🤖 驾驶舱 Agent", _w_text(agent_reply), "reply"))
|
||||
|
||||
return await stream_response(request, agent_stream, 'application/x-ndjson')
|
||||
await sor.C('pipeline_conversations',{'id':getID(),'role':'agent','content':agent_reply,'msg_type':'text','org_id':org_id,'created_by':'system'})
|
||||
|
||||
return json.dumps({"success":True,"agent_reply":agent_reply,"widgets":widgets},ensure_ascii=False)
|
||||
|
||||
elif action == 'list_messages':
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user