Skip to content

Commit 5c8f9fa

Browse files
authored
✨Support Agent switching at conversation start.
2 parents ee3ccdb + 7b0b6ca commit 5c8f9fa

File tree

13 files changed

+454
-16
lines changed

13 files changed

+454
-16
lines changed

backend/apps/agent_app.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from agents.create_agent_info import create_agent_run_info
1010
from consts.model import AgentRequest, AgentInfoRequest, AgentIDRequest, ConversationResponse, AgentImportRequest
1111
from services.agent_service import list_main_agent_info_impl, get_agent_info_impl, \
12-
get_creating_sub_agent_info_impl, update_agent_info_impl, delete_agent_impl, export_agent_impl, import_agent_impl
12+
get_creating_sub_agent_info_impl, update_agent_info_impl, delete_agent_impl, export_agent_impl, import_agent_impl, \
13+
list_all_agent_info_impl
1314
from services.conversation_management_service import save_conversation_user, save_conversation_assistant
1415
from utils.config_utils import config_manager
1516
from utils.thread_utils import submit
@@ -84,7 +85,7 @@ async def reload_config():
8485
return config_manager.force_reload()
8586

8687

87-
@router.get("/list")
88+
@router.get("/list_main_agent_info")
8889
async def list_main_agent_info_api(authorization: str = Header(None)):
8990
"""
9091
List all agents, create if the main Agent cannot be found.
@@ -163,4 +164,15 @@ async def import_agent_api(request: AgentImportRequest, authorization: Optional[
163164
return {}
164165
except Exception as e:
165166
raise HTTPException(status_code=500, detail=f"Agent import error: {str(e)}")
167+
168+
@router.get("/list")
169+
async def list_all_agent_info_api(authorization: Optional[str] = Header(None), request: Request = None):
170+
"""
171+
list all agent info
172+
"""
173+
try:
174+
user_id, tenant_id, _ = get_current_user_info(authorization, request)
175+
return list_all_agent_info_impl(tenant_id=tenant_id, user_id=user_id)
176+
except Exception as e:
177+
raise HTTPException(status_code=500, detail=f"Agent list error: {str(e)}")
166178

backend/database/agent_db.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ def query_or_create_main_agent_id(tenant_id: str, user_id: str) -> int:
5858
main_agent = query.first()
5959

6060
if main_agent is None:
61-
main_agent = create_agent(agent_info={"name": "main",
62-
"description": "",
61+
main_agent = create_agent(agent_info={"name": "manager_agent",
62+
"description": "You are a manager agent capable of invoking other agents and tools.",
6363
"enabled": True}, tenant_id=tenant_id, user_id=user_id)
6464

6565
return main_agent["agent_id"]
@@ -348,4 +348,12 @@ def check_tool_is_available(tool_id_list: List[int]):
348348
tools = session.query(ToolInfo).filter(ToolInfo.tool_id.in_(tool_id_list), ToolInfo.delete_flag != 'Y').all()
349349
return [tool.is_available for tool in tools]
350350

351+
def query_all_agent_info_by_tenant_id(tenant_id: str):
352+
"""
353+
Query all agent info by tenant id
354+
"""
355+
with get_db_session() as session:
356+
agents = session.query(AgentInfo).filter(AgentInfo.tenant_id == tenant_id,
357+
AgentInfo.delete_flag != 'Y').order_by(AgentInfo.create_time.desc()).all()
358+
return [as_dict(agent) for agent in agents]
351359

backend/main_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@
1818
if __name__ == "__main__":
1919
# Scan agents and update to database
2020
import_default_agents_to_pg()
21-
uvicorn.run(app, host="0.0.0.0", port=5010, log_level="warning")
21+
uvicorn.run(app, host="0.0.0.0", port=5010, log_level="info")

backend/services/agent_service.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from database.agent_db import create_agent, query_all_enabled_tool_instances, \
99
query_or_create_main_agent_id, query_sub_agents, search_blank_sub_agent_by_main_agent_id, \
1010
search_tools_for_sub_agent, search_agent_info_by_agent_id, update_agent, delete_agent_by_id, query_all_tools, \
11-
create_or_update_tool_by_tool_info, check_tool_is_available
11+
create_or_update_tool_by_tool_info, check_tool_is_available, query_all_agent_info_by_tenant_id
1212

1313
from utils.auth_utils import get_current_user_id
1414
from typing import Optional
@@ -231,8 +231,8 @@ def import_agent_impl(parent_agent_id: int, agent_info: ExportAndImportAgentInfo
231231
raise ValueError(f"Invalid model name: {agent_info.model_name}. model name must be 'main_model' or 'sub_model'.")
232232
if agent_info.max_steps <= 0 or agent_info.max_steps > 20:
233233
raise ValueError(f"Invalid max steps: {agent_info.max_steps}. max steps must be greater than 0 and less than 20.")
234-
if agent_info.name == "main":
235-
raise ValueError(f"Invalid agent name: {agent_info.name}. agent name cannot be 'main'.")
234+
if not agent_info.name.isidentifier():
235+
raise ValueError(f"Invalid agent name: {agent_info.name}. agent name must be a valid python variable name.")
236236
# create a new agent
237237
user_id, tenant_id = get_current_user_id()
238238
new_agent = create_agent(agent_info={"name": agent_info.name,
@@ -307,3 +307,38 @@ def import_default_agents_to_pg():
307307
except Exception as e:
308308
logger.error(f"Failed to import default agents: {str(e)}")
309309
raise ValueError(f"Failed to import default agents: {str(e)}")
310+
311+
def list_all_agent_info_impl(tenant_id: str, user_id: str) -> list[dict]:
312+
"""
313+
list all agent info
314+
315+
Args:
316+
tenant_id (str): tenant id
317+
user_id (str): user id
318+
319+
Raises:
320+
ValueError: failed to query all agent info
321+
322+
Returns:
323+
list: list of agent info
324+
"""
325+
try:
326+
agent_list = query_all_agent_info_by_tenant_id(tenant_id=tenant_id)
327+
328+
simple_agent_list = []
329+
for agent in agent_list:
330+
# check agent is available
331+
tool_info = search_tools_for_sub_agent(agent_id=agent["agent_id"], tenant_id=tenant_id, user_id=None)
332+
tool_id_list = [tool["tool_id"] for tool in tool_info]
333+
is_available = all(check_tool_is_available(tool_id_list))
334+
335+
simple_agent_list.append({
336+
"agent_id": agent["agent_id"],
337+
"name": agent["name"],
338+
"description": agent["description"],
339+
"is_available": is_available
340+
})
341+
return simple_agent_list
342+
except Exception as e:
343+
logger.error(f"Failed to query all agent info: {str(e)}")
344+
raise ValueError(f"Failed to query all agent info: {str(e)}")

frontend/app/[locale]/chat/internal/chatInterface.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ export function ChatInterface() {
102102
// Add force scroll to bottom state control
103103
const [shouldScrollToBottom, setShouldScrollToBottom] = useState(false);
104104

105+
// Add agent selection state
106+
const [selectedAgentId, setSelectedAgentId] = useState<number | null>(null);
107+
105108
// Reset scroll to bottom state
106109
useEffect(() => {
107110
if (shouldScrollToBottom) {
@@ -514,7 +517,7 @@ export function ChatInterface() {
514517
}
515518

516519
// Send request to backend API, add signal parameter
517-
const reader = await conversationService.runAgent({
520+
const runAgentParams: any = {
518521
query: finalQuery, // Use preprocessed query or original query
519522
conversation_id: currentConversationId,
520523
is_set: isSwitchedConversation || currentMessages.length <= 1,
@@ -540,7 +543,14 @@ export function ChatInterface() {
540543
description: description
541544
};
542545
}) : undefined // Use complete attachment object structure
543-
}, currentController.signal);
546+
};
547+
548+
// Only add agent_id if it's not null
549+
if (selectedAgentId !== null) {
550+
runAgentParams.agent_id = selectedAgentId;
551+
}
552+
553+
const reader = await conversationService.runAgent(runAgentParams, currentController.signal);
544554

545555
if (!reader) throw new Error("Response body is null")
546556

@@ -1327,6 +1337,8 @@ export function ChatInterface() {
13271337
onOpinionChange={handleOpinionChange}
13281338
currentConversationId={conversationId}
13291339
shouldScrollToBottom={shouldScrollToBottom}
1340+
selectedAgentId={selectedAgentId}
1341+
onAgentSelect={setSelectedAgentId}
13301342
/>
13311343

13321344
{/* Footer */}

frontend/app/[locale]/chat/layout/chatInput.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
} from "react-icons/ai"
2929
import { extractColorsFromUri } from "@/lib/avatar"
3030
import { useTranslation } from "react-i18next"
31+
import { AgentSelector } from "@/components/ui/agentSelector"
3132

3233
// Image viewer component
3334
function ImageViewer({ src, alt, onClose }: { src: string, alt: string, onClose: () => void }) {
@@ -307,6 +308,8 @@ interface ChatInputProps {
307308
onImageUpload?: (file: File) => void
308309
attachments?: FilePreview[]
309310
onAttachmentsChange?: (attachments: FilePreview[]) => void
311+
selectedAgentId?: number | null
312+
onAgentSelect?: (agentId: number | null) => void
310313
}
311314

312315
export function ChatInput({
@@ -322,7 +325,9 @@ export function ChatInput({
322325
onFileUpload,
323326
onImageUpload,
324327
attachments = [],
325-
onAttachmentsChange
328+
onAttachmentsChange,
329+
selectedAgentId = null,
330+
onAgentSelect
326331
}: ChatInputProps) {
327332
const [isRecording, setIsRecording] = useState(false)
328333
const [recordingStatus, setRecordingStatus] = useState<"idle" | "recording" | "connecting" | "error">("idle")
@@ -883,6 +888,16 @@ export function ChatInput({
883888
/>
884889
</div>
885890
<div className="h-12 bg-slate-100 relative">
891+
{/* Agent selector on the left */}
892+
<div className="absolute left-3 top-[40%] -translate-y-1/2">
893+
<AgentSelector
894+
selectedAgentId={selectedAgentId}
895+
onAgentSelect={onAgentSelect || (() => {})}
896+
disabled={isLoading || isStreaming}
897+
isInitialMode={isInitialMode}
898+
/>
899+
</div>
900+
886901
<div className="absolute right-3 top-[40%] -translate-y-1/2 flex items-center space-x-1">
887902
{/* Voice to text button */}
888903
<TooltipProvider>

frontend/app/[locale]/chat/streaming/chatStreamMain.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ interface ChatStreamMainProps {
3939
onOpinionChange?: (messageId: number, opinion: 'Y' | 'N' | null) => void
4040
currentConversationId?: number
4141
shouldScrollToBottom?: boolean
42+
selectedAgentId?: number | null
43+
onAgentSelect?: (agentId: number | null) => void
4244
}
4345

4446
export function ChatStreamMain({
@@ -62,6 +64,8 @@ export function ChatStreamMain({
6264
onOpinionChange,
6365
currentConversationId,
6466
shouldScrollToBottom,
67+
selectedAgentId,
68+
onAgentSelect,
6569
}: ChatStreamMainProps) {
6670
const { t } = useTranslation();
6771
// Animation variants for ChatInput
@@ -424,6 +428,8 @@ export function ChatStreamMain({
424428
onAttachmentsChange={onAttachmentsChange}
425429
onFileUpload={onFileUpload}
426430
onImageUpload={onImageUpload}
431+
selectedAgentId={selectedAgentId}
432+
onAgentSelect={onAgentSelect}
427433
/>
428434
</motion.div>
429435
</AnimatePresence>
@@ -505,6 +511,8 @@ export function ChatStreamMain({
505511
onAttachmentsChange={onAttachmentsChange}
506512
onFileUpload={onFileUpload}
507513
onImageUpload={onImageUpload}
514+
selectedAgentId={selectedAgentId}
515+
onAgentSelect={onAgentSelect}
508516
/>
509517
</motion.div>
510518
</AnimatePresence>

0 commit comments

Comments
 (0)