File size: 5,523 Bytes
2a5c31d a67fbf4 2a5c31d a67fbf4 2a5c31d a67fbf4 2a5c31d a67fbf4 2a5c31d a67fbf4 2a5c31d a67fbf4 2a5c31d a67fbf4 2a5c31d a67fbf4 2a5c31d a67fbf4 2a5c31d a67fbf4 2a5c31d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | import uuid
import json
from typing import Dict, List, Optional, Tuple
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, HTTPException
from pydantic import BaseModel
from src.agents import executor, registry
from src.agents.chat import ChatSession
from src.agents.base import AgentContext
router = APIRouter(prefix="/chat", tags=["chat"])
# In-memory session store
_sessions: Dict[str, ChatSession] = {}
def _parse_agent_command(message: str) -> Tuple[Optional[str], Dict[str, object]]:
text = message.strip()
if not (text.startswith("/agent ") or text.startswith("/call ")):
return None, {}
parts = text.split(maxsplit=2)
if len(parts) < 2:
return None, {}
agent_type = parts[1].strip()
if len(parts) == 2:
return agent_type, {"message": ""}
payload = parts[2].strip()
if payload.startswith("{"):
try:
parsed = json.loads(payload)
if isinstance(parsed, dict):
return agent_type, parsed
except json.JSONDecodeError:
pass
return agent_type, {"message": payload}
async def _run_agent(agent_type: str, message: str, kwargs: Dict[str, object], session: ChatSession):
agent = registry.create(agent_type)
ctx = AgentContext(agent_id=agent.agent_id)
if agent_type in ("assistant", "router") and "message" not in kwargs:
kwargs = {**kwargs, "message": message}
result = await executor.run(agent, ctx, session=session, **kwargs)
if result.output:
if isinstance(result.output, dict):
reply = result.output.get("reply")
if reply is None:
reply = json.dumps(result.output, indent=2, default=str)
else:
reply = str(result.output)
else:
reply = result.error or "Agent failed"
return reply
class ChatRequest(BaseModel):
session_id: Optional[str] = None
agent_type: str = "assistant"
message: str
class ChatResponse(BaseModel):
session_id: str
reply: str
history: list
@router.post("", response_model=ChatResponse)
async def chat(req: ChatRequest):
session_id = req.session_id or str(uuid.uuid4())
session = _sessions.setdefault(session_id, ChatSession(session_id=session_id))
session.add("user", req.message)
direct_agent_type, direct_kwargs = _parse_agent_command(req.message)
if direct_agent_type:
try:
reply = await _run_agent(direct_agent_type, req.message, direct_kwargs, session)
except KeyError:
raise HTTPException(status_code=404, detail=f"Agent type '{direct_agent_type}' not found")
return ChatResponse(
session_id=session_id,
reply=reply,
history=[{"role": m.role, "content": m.content} for m in session.history],
)
try:
agent = registry.create(req.agent_type)
except KeyError:
raise HTTPException(status_code=404, detail=f"Agent type '{req.agent_type}' not found")
ctx = AgentContext(agent_id=agent.agent_id)
result = await executor.run(agent, ctx, session=session, message=req.message)
if result.output:
return ChatResponse(
session_id=session_id,
reply=result.output.get("reply", ""),
history=result.output.get("history", []),
)
raise HTTPException(status_code=500, detail=result.error or "Agent failed")
@router.get("/sessions")
async def list_sessions():
return [
{"session_id": sid, "turns": len(s.history)}
for sid, s in _sessions.items()
]
@router.get("/sessions/{session_id}")
async def get_session(session_id: str):
session = _sessions.get(session_id)
if not session:
raise HTTPException(status_code=404, detail="Session not found")
return {"session_id": session_id,
"history": [{"role": m.role, "content": m.content} for m in session.history]}
@router.delete("/sessions/{session_id}")
async def delete_session(session_id: str):
_sessions.pop(session_id, None)
return {"deleted": session_id}
# ββ WebSocket ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@router.websocket("/ws/{session_id}")
async def chat_ws(websocket: WebSocket, session_id: str, agent_type: str = "assistant"):
await websocket.accept()
session = _sessions.setdefault(session_id, ChatSession(session_id=session_id))
try:
while True:
message = await websocket.receive_text()
session.add("user", message)
direct_agent_type, direct_kwargs = _parse_agent_command(message)
try:
if direct_agent_type:
reply = await _run_agent(direct_agent_type, message, direct_kwargs, session)
else:
agent = registry.create(agent_type)
ctx = AgentContext(agent_id=agent.agent_id)
result = await executor.run(agent, ctx, session=session, message=message)
reply = result.output.get("reply", "") if result.output else (result.error or "error")
except Exception as exc:
reply = f"[Error] {exc}"
await websocket.send_json({
"session_id": session_id,
"reply": reply,
"turns": len(session.history),
})
except WebSocketDisconnect:
pass
|