fix(backend): 修复全面审阅发现的核心问题
修复索引首次失败回滚、Vault 扫描边界、Markdown 代码围栏和 UTF-16 Citation 偏移。 收紧 Plugin 权限与 JSON Schema 校验,补齐 Note Move、Task、Attachment 和 Transcript Tool。 接入 OpenAI SSE 与 Ollama JSONL 真流式输出,修正 Provider PATCH 语义并限制运行时内存保留。 新增对应回归测试,后端测试增至 62 项。
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from app.agent.tools import ToolExecutionContext, ToolRegistry
|
||||
from app.contracts import SearchMode, SearchRequest, ToolDefinition
|
||||
from app.contracts import SearchMode, SearchRequest, TaskStatus, ToolDefinition
|
||||
from app.retrieval.engine import engine
|
||||
from app.services import note_service
|
||||
from app.services import attachment_service, task_service, transcription_service
|
||||
|
||||
|
||||
class ToolArguments(BaseModel):
|
||||
@@ -54,6 +57,42 @@ class NoteListArguments(ToolArguments):
|
||||
tag: str | None = None
|
||||
|
||||
|
||||
class NoteMoveArguments(ToolArguments):
|
||||
note_id: str = Field(min_length=1)
|
||||
folder: str
|
||||
|
||||
|
||||
class TaskCreateArguments(ToolArguments):
|
||||
title: str = Field(min_length=1)
|
||||
description: str = ""
|
||||
note_id: str | None = None
|
||||
due_at: datetime | None = None
|
||||
|
||||
|
||||
class TaskUpdateArguments(ToolArguments):
|
||||
task_id: str = Field(min_length=1)
|
||||
title: str | None = None
|
||||
description: str | None = None
|
||||
status: TaskStatus | None = None
|
||||
note_id: str | None = None
|
||||
due_at: datetime | None = None
|
||||
|
||||
|
||||
class TaskListArguments(ToolArguments):
|
||||
limit: int = Field(default=50, ge=1, le=100)
|
||||
offset: int = Field(default=0, ge=0)
|
||||
|
||||
|
||||
class AttachmentReadArguments(ToolArguments):
|
||||
attachment_id: str = Field(min_length=1)
|
||||
max_chars: int = Field(default=100_000, ge=1, le=1_000_000)
|
||||
|
||||
|
||||
class AudioTranscribeArguments(ToolArguments):
|
||||
attachment_id: str = Field(min_length=1)
|
||||
language: str | None = None
|
||||
|
||||
|
||||
async def echo(arguments: EchoArguments, _: ToolExecutionContext) -> dict[str, str]:
|
||||
return {"text": arguments.text}
|
||||
|
||||
@@ -94,6 +133,39 @@ def list_notes(arguments: NoteListArguments, _: ToolExecutionContext) -> dict:
|
||||
}
|
||||
|
||||
|
||||
async def move_note(arguments: NoteMoveArguments, _: ToolExecutionContext) -> dict:
|
||||
note = await note_service.move_note(arguments.note_id, folder=arguments.folder)
|
||||
return note.model_dump(mode="json")
|
||||
|
||||
|
||||
def create_task(arguments: TaskCreateArguments, _: ToolExecutionContext) -> dict:
|
||||
return task_service.create_task(**arguments.model_dump()).model_dump(mode="json")
|
||||
|
||||
|
||||
def update_task(arguments: TaskUpdateArguments, _: ToolExecutionContext) -> dict:
|
||||
values = arguments.model_dump(exclude_unset=True)
|
||||
task_id = values.pop("task_id")
|
||||
return task_service.update_task(task_id, values).model_dump(mode="json")
|
||||
|
||||
|
||||
def list_tasks(arguments: TaskListArguments, _: ToolExecutionContext) -> dict:
|
||||
items, total = task_service.list_tasks(**arguments.model_dump())
|
||||
return {
|
||||
"items": [item.model_dump(mode="json") for item in items],
|
||||
"page": {"total": total, "limit": arguments.limit, "offset": arguments.offset},
|
||||
}
|
||||
|
||||
|
||||
def read_attachment(arguments: AttachmentReadArguments, _: ToolExecutionContext) -> dict:
|
||||
return attachment_service.read_attachment(**arguments.model_dump())
|
||||
|
||||
|
||||
def transcribe_audio(arguments: AudioTranscribeArguments, _: ToolExecutionContext) -> dict:
|
||||
return transcription_service.create_transcription(
|
||||
arguments.attachment_id, arguments.language
|
||||
).model_dump(mode="json")
|
||||
|
||||
|
||||
def _register(
|
||||
registry: ToolRegistry,
|
||||
*,
|
||||
@@ -178,3 +250,51 @@ def register_builtin_tools(registry: ToolRegistry) -> None:
|
||||
executor=list_notes,
|
||||
permission="notes.read",
|
||||
)
|
||||
_register(
|
||||
registry,
|
||||
name="notes.move",
|
||||
description="Move a note to another folder while preserving note_id.",
|
||||
arguments_model=NoteMoveArguments,
|
||||
executor=move_note,
|
||||
permission="notes.write",
|
||||
)
|
||||
_register(
|
||||
registry,
|
||||
name="tasks.create",
|
||||
description="Create a persistent task.",
|
||||
arguments_model=TaskCreateArguments,
|
||||
executor=create_task,
|
||||
permission="tasks.write",
|
||||
)
|
||||
_register(
|
||||
registry,
|
||||
name="tasks.update",
|
||||
description="Update a persistent task.",
|
||||
arguments_model=TaskUpdateArguments,
|
||||
executor=update_task,
|
||||
permission="tasks.write",
|
||||
)
|
||||
_register(
|
||||
registry,
|
||||
name="tasks.list",
|
||||
description="List persistent tasks.",
|
||||
arguments_model=TaskListArguments,
|
||||
executor=list_tasks,
|
||||
permission="tasks.read",
|
||||
)
|
||||
_register(
|
||||
registry,
|
||||
name="attachments.read",
|
||||
description="Read a UTF-8 attachment from host-managed attachment storage.",
|
||||
arguments_model=AttachmentReadArguments,
|
||||
executor=read_attachment,
|
||||
permission="attachments.read",
|
||||
)
|
||||
_register(
|
||||
registry,
|
||||
name="audio.transcribe",
|
||||
description="Read a host-generated transcript for an audio attachment.",
|
||||
arguments_model=AudioTranscribeArguments,
|
||||
executor=transcribe_audio,
|
||||
permission="attachments.read",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user