Add reusable tools and assistant bindings
- Introduce a new `Tool` model and `AssistantToolBinding` for managing reusable tools within the application. - Implement CRUD operations for tools in the new `tools` route, allowing for the creation, retrieval, updating, and deletion of tools. - Update the `Assistant` model to include a list of tool IDs, enabling assistants to utilize these tools. - Enhance the backend routes to synchronize tool bindings with assistants, ensuring proper management of tool associations. - Add frontend components for tool management, including a tool picker in the assistant configuration, improving user experience in tool selection. - Create a mobile call page to facilitate video calls, integrating camera and microphone selection for enhanced communication capabilities. - Update API definitions to include tool-related types and operations, ensuring consistency across the application. - Add a migration script to create the necessary database tables for tools and bindings, supporting the new functionality.
This commit is contained in:
@@ -2,7 +2,13 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from db.models import Assistant, AssistantModelBinding, ModelResource
|
||||
from db.models import (
|
||||
Assistant,
|
||||
AssistantModelBinding,
|
||||
AssistantToolBinding,
|
||||
ModelResource,
|
||||
Tool,
|
||||
)
|
||||
from db.session import get_session
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from schemas import AssistantOut, AssistantUpsert
|
||||
@@ -83,6 +89,46 @@ async def _resource_ids(session: AsyncSession, assistant_id: str) -> dict[str, s
|
||||
return {binding.capability: binding.model_resource_id for binding in bindings}
|
||||
|
||||
|
||||
async def _sync_tool_bindings(
|
||||
session: AsyncSession, assistant_id: str, assistant_type: str, tool_ids: list[str]
|
||||
) -> None:
|
||||
requested = list(dict.fromkeys(tool_ids)) if assistant_type == "prompt" else []
|
||||
if requested:
|
||||
tools = (
|
||||
await session.execute(select(Tool).where(Tool.id.in_(requested)))
|
||||
).scalars().all()
|
||||
found = {tool.id for tool in tools if tool.status == "active"}
|
||||
missing = [tool_id for tool_id in requested if tool_id not in found]
|
||||
if missing:
|
||||
raise HTTPException(400, f"工具不存在或未启用: {', '.join(missing)}")
|
||||
|
||||
existing = (
|
||||
await session.execute(
|
||||
select(AssistantToolBinding).where(
|
||||
AssistantToolBinding.assistant_id == assistant_id
|
||||
)
|
||||
)
|
||||
).scalars().all()
|
||||
existing_by_id = {binding.tool_id: binding for binding in existing}
|
||||
for tool_id, binding in existing_by_id.items():
|
||||
if tool_id not in requested:
|
||||
await session.delete(binding)
|
||||
for tool_id in requested:
|
||||
if tool_id not in existing_by_id:
|
||||
session.add(AssistantToolBinding(assistant_id=assistant_id, tool_id=tool_id))
|
||||
|
||||
|
||||
async def _tool_ids(session: AsyncSession, assistant_id: str) -> list[str]:
|
||||
rows = (
|
||||
await session.execute(
|
||||
select(AssistantToolBinding.tool_id)
|
||||
.where(AssistantToolBinding.assistant_id == assistant_id)
|
||||
.order_by(AssistantToolBinding.created_at)
|
||||
)
|
||||
).scalars().all()
|
||||
return list(rows)
|
||||
|
||||
|
||||
async def _to_out(session: AsyncSession, assistant: Assistant) -> AssistantOut:
|
||||
return AssistantOut(
|
||||
id=assistant.id,
|
||||
@@ -95,6 +141,7 @@ async def _to_out(session: AsyncSession, assistant: Assistant) -> AssistantOut:
|
||||
vision_model_resource_id=assistant.vision_model_resource_id,
|
||||
model_resource_ids=await _resource_ids(session, assistant.id),
|
||||
knowledge_base_id=assistant.knowledge_base_id,
|
||||
tool_ids=await _tool_ids(session, assistant.id),
|
||||
prompt=assistant.prompt,
|
||||
api_url=assistant.api_url,
|
||||
api_key=mask(assistant.api_key),
|
||||
@@ -120,10 +167,12 @@ async def create_assistant(
|
||||
await _validate_vision_model(session, body)
|
||||
data = body.model_dump()
|
||||
resource_ids = data.pop("model_resource_ids")
|
||||
tool_ids = data.pop("tool_ids")
|
||||
assistant = Assistant(id=f"asst_{uuid.uuid4().hex[:12]}", **data)
|
||||
session.add(assistant)
|
||||
await session.flush()
|
||||
await _sync_bindings(session, assistant.id, resource_ids)
|
||||
await _sync_tool_bindings(session, assistant.id, assistant.type, tool_ids)
|
||||
await session.commit()
|
||||
await session.refresh(assistant)
|
||||
return await _to_out(session, assistant)
|
||||
@@ -165,6 +214,9 @@ async def duplicate_assistant(
|
||||
session.add(assistant)
|
||||
await session.flush()
|
||||
await _sync_bindings(session, assistant.id, await _resource_ids(session, source.id))
|
||||
await _sync_tool_bindings(
|
||||
session, assistant.id, assistant.type, await _tool_ids(session, source.id)
|
||||
)
|
||||
await session.commit()
|
||||
await session.refresh(assistant)
|
||||
return await _to_out(session, assistant)
|
||||
@@ -183,10 +235,12 @@ async def update_assistant(
|
||||
await _validate_vision_model(session, body)
|
||||
data = body.model_dump()
|
||||
resource_ids = data.pop("model_resource_ids")
|
||||
tool_ids = data.pop("tool_ids")
|
||||
data["api_key"] = resolve_incoming_key(data["api_key"], assistant.api_key)
|
||||
for key, value in data.items():
|
||||
setattr(assistant, key, value)
|
||||
await _sync_bindings(session, assistant.id, resource_ids)
|
||||
await _sync_tool_bindings(session, assistant.id, assistant.type, tool_ids)
|
||||
await session.commit()
|
||||
await session.refresh(assistant)
|
||||
return await _to_out(session, assistant)
|
||||
|
||||
Reference in New Issue
Block a user