From 1130cabe57c6b47ce289f8be250a0122a67ebb01 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Fri, 6 Mar 2026 10:14:05 +0800 Subject: [PATCH 01/24] =?UTF-8?q?fix(tool):=20=E4=BF=AE=E5=A4=8D=E9=83=A8?= =?UTF-8?q?=E5=88=86=E6=83=85=E5=86=B5=E4=B8=8B=E5=B7=A5=E5=85=B7=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E5=BC=82=E5=B8=B8=E4=B8=AD=E6=96=AD=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agents/common/toolkits/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/common/toolkits/registry.py b/src/agents/common/toolkits/registry.py index ee0eee6f..9cfe8a5b 100644 --- a/src/agents/common/toolkits/registry.py +++ b/src/agents/common/toolkits/registry.py @@ -43,7 +43,7 @@ def tool( name_or_callable: str | Callable | None = None, description: str | None = None, args_schema: type | None = None, - return_direct: bool = True, + return_direct: bool = False, ): """基于 langchain.tool 的拓展装饰器,同时注册元数据 From 96ce4dbe8af78e80ed7601362da8b596995eaa9c Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Fri, 6 Mar 2026 10:14:43 +0800 Subject: [PATCH 02/24] =?UTF-8?q?feat(chat):=20=E6=96=B0=E5=A2=9E=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E7=BA=BF=E7=A8=8B=E5=88=97=E8=A1=A8=E7=9A=84=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81limit?= =?UTF-8?q?=E5=92=8Coffset=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/chat_router.py | 10 +- src/repositories/conversation_repository.py | 11 +- src/services/conversation_service.py | 4 + web/src/apis/agent_api.js | 6 +- web/src/components/AgentChatComponent.vue | 38 ++++- web/src/components/ChatSidebarComponent.vue | 159 +++++++++----------- web/src/components/DebugComponent.vue | 1 - web/src/utils/time.js | 5 +- 8 files changed, 137 insertions(+), 97 deletions(-) diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py index acc3575d..0f29ef43 100644 --- a/server/routers/chat_router.py +++ b/server/routers/chat_router.py @@ -706,10 +706,16 @@ async def create_thread( @chat.get("/threads", response_model=list[ThreadResponse]) async def list_threads( - agent_id: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_required_user) + agent_id: str, + limit: int = Query(100, ge=1, le=500), + offset: int = Query(0, ge=0), + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_required_user), ): """获取用户的所有对话线程 (使用新存储系统)""" - return await list_threads_view(agent_id=agent_id, db=db, current_user_id=str(current_user.id)) + return await list_threads_view( + agent_id=agent_id, db=db, current_user_id=str(current_user.id), limit=limit, offset=offset + ) @chat.delete("/thread/{thread_id}") diff --git a/src/repositories/conversation_repository.py b/src/repositories/conversation_repository.py index 018fb180..7e0fabcd 100644 --- a/src/repositories/conversation_repository.py +++ b/src/repositories/conversation_repository.py @@ -196,7 +196,12 @@ class ConversationRepository: return await self.get_messages(conversation.id, limit, offset) async def list_conversations( - self, user_id: str | None = None, agent_id: str | None = None, status: str = "active" + self, + user_id: str | None = None, + agent_id: str | None = None, + status: str = "active", + limit: int | None = None, + offset: int = 0, ) -> list[Conversation]: query = select(Conversation).where(Conversation.status == status) @@ -206,6 +211,10 @@ class ConversationRepository: query = query.where(Conversation.agent_id == agent_id) query = query.order_by(Conversation.updated_at.desc()) + + if limit: + query = query.limit(limit).offset(offset) + result = await self.db.execute(query) return list(result.scalars().all()) diff --git a/src/services/conversation_service.py b/src/services/conversation_service.py index d03d478d..7c9743b1 100644 --- a/src/services/conversation_service.py +++ b/src/services/conversation_service.py @@ -156,6 +156,8 @@ async def list_threads_view( agent_id: str, db: AsyncSession, current_user_id: str, + limit: int | None = None, + offset: int = 0, ) -> list[dict]: if not agent_id: raise HTTPException(status_code=422, detail="agent_id 不能为空") @@ -165,6 +167,8 @@ async def list_threads_view( user_id=str(current_user_id), agent_id=agent_id, status="active", + limit=limit, + offset=offset, ) return [ diff --git a/web/src/apis/agent_api.js b/web/src/apis/agent_api.js index cd2ec56a..4dcce5d9 100644 --- a/web/src/apis/agent_api.js +++ b/web/src/apis/agent_api.js @@ -286,10 +286,12 @@ export const threadApi = { /** * 获取对话线程列表 * @param {string} agentId - 智能体ID + * @param {number} limit - 返回数量限制,默认100 + * @param {number} offset - 偏移量,默认0 * @returns {Promise} - 对话线程列表 */ - getThreads: (agentId) => { - const url = `/api/chat/threads?agent_id=${agentId}` + getThreads: (agentId, limit = 100, offset = 0) => { + const url = `/api/chat/threads?agent_id=${agentId}&limit=${limit}&offset=${offset}` return apiGet(url) }, diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 08a48235..cf2c836b 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -9,12 +9,15 @@ :agents="agents" :selected-agent-id="currentAgentId" :is-creating-new-chat="chatUIStore.creatingNewChat" + :has-more-chats="hasMoreChats" + :is-loading-more="isLoadingMoreChats" @create-chat="createNewChat" @select-chat="selectChat" @delete-chat="deleteChat" @rename-chat="renameChat" @toggle-sidebar="toggleSidebar" @open-agent-modal="openAgentModal" + @load-more-chats="loadMoreChats" :class="{ 'sidebar-open': chatUIStore.isSidebarOpen, 'no-transition': localUIState.isInitialRender @@ -286,6 +289,8 @@ const chatState = reactive({ // 组件级别的线程和消息状态 const threads = ref([]) const threadMessages = ref({}) +const hasMoreChats = ref(true) // 是否还有更多对话可加载 +const isLoadingMoreChats = ref(false) // 加载更多对话中 // 本地 UI 状态(仅在本组件使用) const localUIState = reactive({ @@ -588,8 +593,10 @@ const fetchThreads = async (agentId = null) => { chatUIStore.isLoadingThreads = true try { - const fetchedThreads = await threadApi.getThreads(targetAgentId) + const fetchedThreads = await threadApi.getThreads(targetAgentId, 100, 0) threads.value = fetchedThreads || [] + // 如果返回的数量小于limit,说明没有更多了 + hasMoreChats.value = fetchedThreads && fetchedThreads.length >= 100 } catch (error) { console.error('Failed to fetch threads:', error) handleChatError(error, 'fetch') @@ -599,6 +606,31 @@ const fetchThreads = async (agentId = null) => { } } +// 加载更多对话 +const loadMoreChats = async () => { + if (isLoadingMoreChats.value || !hasMoreChats.value) return + + const targetAgentId = currentAgentId.value + if (!targetAgentId) return + + isLoadingMoreChats.value = true + try { + const offset = threads.value.length + const fetchedThreads = await threadApi.getThreads(targetAgentId, 100, offset) + if (fetchedThreads && fetchedThreads.length > 0) { + threads.value = [...threads.value, ...fetchedThreads] + hasMoreChats.value = fetchedThreads.length >= 100 + } else { + hasMoreChats.value = false + } + } catch (error) { + console.error('Failed to load more chats:', error) + handleChatError(error, 'fetch') + } finally { + isLoadingMoreChats.value = false + } +} + // 创建新线程 const createThread = async (agentId, title = '新的对话') => { if (!agentId) return null @@ -1234,10 +1266,6 @@ const createNewChat = async () => { // 如果第一个对话为空,直接切换到第一个对话而不是创建新对话 if (await switchToFirstChatIfEmpty()) return - // 只有当当前对话是第一个对话且为空时,才阻止创建新对话 - const currentThreadIndex = threads.value.findIndex((thread) => thread.id === currentChatId.value) - if (currentChatId.value && conversations.value.length === 0 && currentThreadIndex === 0) return - chatUIStore.creatingNewChat = true try { const newThread = await createThread(currentAgentId.value, '新的对话') diff --git a/web/src/components/ChatSidebarComponent.vue b/web/src/components/ChatSidebarComponent.vue index a0a86c40..01bd4e6e 100644 --- a/web/src/components/ChatSidebarComponent.vue +++ b/web/src/components/ChatSidebarComponent.vue @@ -29,54 +29,61 @@
- From 9a0541eb7d5ef50ad0726d3469b56f8ee443819f Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Tue, 10 Mar 2026 00:16:41 +0800 Subject: [PATCH 21/24] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E8=BF=87?= =?UTF-8?q?=E6=97=B6=E7=9A=84=E5=AE=A1=E6=89=B9=E9=80=BB=E8=BE=91=E5=92=8C?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E9=80=89=E9=A1=B9=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agents/common/toolkits/debug/__init__.py | 5 +- src/agents/common/toolkits/debug/tools.py | 59 ------- src/services/chat_stream_service.py | 8 - test/test_chat_stream_interrupt.py | 161 +++++++++++++++++++ web/src/composables/useApproval.js | 37 +---- 5 files changed, 166 insertions(+), 104 deletions(-) create mode 100644 test/test_chat_stream_interrupt.py diff --git a/src/agents/common/toolkits/debug/__init__.py b/src/agents/common/toolkits/debug/__init__.py index 780a0fd8..649aff73 100644 --- a/src/agents/common/toolkits/debug/__init__.py +++ b/src/agents/common/toolkits/debug/__init__.py @@ -1,6 +1,3 @@ # debug 工具包 -from .tools import get_approved_user_goal -__all__ = [ - "get_approved_user_goal", -] +__all__ = [] diff --git a/src/agents/common/toolkits/debug/tools.py b/src/agents/common/toolkits/debug/tools.py index c85ae9b0..e69de29b 100644 --- a/src/agents/common/toolkits/debug/tools.py +++ b/src/agents/common/toolkits/debug/tools.py @@ -1,59 +0,0 @@ -from langgraph.types import interrupt - -from src.agents.common.toolkits.registry import tool - - -@tool(category="debug", tags=["内置", "审批"], display_name="人工审批") -def get_approved_user_goal( - operation_description: str, -) -> dict: - """ - 请求人工审批,在执行重要操作前获得人类确认。 - - Args: - operation_description: 需要审批的操作描述,例如 "调用知识库工具" - Returns: - dict: 包含审批结果的字典,格式为 {"approved": bool, "message": str} - """ - # 构建详细的中断信息 - interrupt_info = { - "question": "是否批准以下操作?", - "operation": operation_description, - } - - # 触发人工审批 - interrupt_result = interrupt(interrupt_info) - - if isinstance(interrupt_result, bool): - is_approved = interrupt_result - elif isinstance(interrupt_result, str): - is_approved = interrupt_result.strip().lower() in {"approve", "approved", "true", "yes", "1"} - elif isinstance(interrupt_result, list): - lowered = {str(item).strip().lower() for item in interrupt_result} - is_approved = "approve" in lowered or "approved" in lowered - elif isinstance(interrupt_result, dict): - selected = interrupt_result.get("selected") - if isinstance(selected, list): - lowered = {str(item).strip().lower() for item in selected} - is_approved = "approve" in lowered or "approved" in lowered - else: - text = str(interrupt_result.get("text") or "").strip().lower() - is_approved = text in {"approve", "approved", "true", "yes", "1"} - else: - is_approved = bool(interrupt_result) - - # 返回审批结果 - if is_approved: - result = { - "approved": True, - "message": f"✅ 操作已批准:{operation_description}", - } - print(f"✅ 人工审批通过: {operation_description}") - else: - result = { - "approved": False, - "message": f"❌ 操作被拒绝:{operation_description}", - } - print(f"❌ 人工审批被拒绝: {operation_description}") - - return result diff --git a/src/services/chat_stream_service.py b/src/services/chat_stream_service.py index 7539d50d..9133ad7a 100644 --- a/src/services/chat_stream_service.py +++ b/src/services/chat_stream_service.py @@ -256,14 +256,6 @@ def _build_ask_user_question_payload(info: Any, thread_id: str) -> dict[str, Any operation = payload.get("operation") options = _normalize_interrupt_options(payload.get("options")) - if not options and isinstance(operation, str) and operation.strip(): - # 兼容旧版 get_approved_user_goal 的 interrupt 结构 - options = [ - {"label": "批准 (Recommended)", "value": "approve"}, - {"label": "拒绝", "value": "reject"}, - ] - source = "get_approved_user_goal" - allow_other = False return { "question_id": question_id, diff --git a/test/test_chat_stream_interrupt.py b/test/test_chat_stream_interrupt.py new file mode 100644 index 00000000..14ef11fe --- /dev/null +++ b/test/test_chat_stream_interrupt.py @@ -0,0 +1,161 @@ +"""测试 chat_stream_service 中的 interrupt 相关函数""" +import pytest +import sys +import os + +sys.path.insert(0, os.getcwd()) + +from src.services.chat_stream_service import ( + _normalize_interrupt_options, + _build_ask_user_question_payload, + _coerce_interrupt_payload, +) + + +class TestNormalizeInterruptOptions: + """测试 _normalize_interrupt_options 函数""" + + def test_empty_input(self): + assert _normalize_interrupt_options(None) == [] + assert _normalize_interrupt_options([]) == [] + + def test_dict_options(self): + raw = [ + {"label": "选项1", "value": "option1"}, + {"label": "选项2", "value": "option2"}, + ] + result = _normalize_interrupt_options(raw) + assert len(result) == 2 + assert result[0] == {"label": "选项1", "value": "option1"} + assert result[1] == {"label": "选项2", "value": "option2"} + + def test_string_options(self): + raw = ["选项1", "选项2", "选项3"] + result = _normalize_interrupt_options(raw) + assert len(result) == 3 + assert result[0] == {"label": "选项1", "value": "选项1"} + + def test_mixed_options(self): + raw = [{"label": "选项1", "value": "option1"}, "选项2"] + result = _normalize_interrupt_options(raw) + assert len(result) == 2 + assert result[0] == {"label": "选项1", "value": "option1"} + assert result[1] == {"label": "选项2", "value": "选项2"} + + def test_invalid_options(self): + raw = [{"label": "只有label"}, {}, " "] + result = _normalize_interrupt_options(raw) + assert len(result) == 1 # 只有有效的选项 + assert result[0] == {"label": "只有label", "value": "只有label"} + + def test_value_only(self): + raw = [{"value": "only_value"}] + result = _normalize_interrupt_options(raw) + assert len(result) == 1 + assert result[0] == {"label": "only_value", "value": "only_value"} + + +class TestBuildAskUserQuestionPayload: + """测试 _build_ask_user_question_payload 函数""" + + def test_basic_question(self): + info = { + "question": "请确认是否继续?", + "options": [ + {"label": "确认", "value": "yes"}, + {"label": "取消", "value": "no"}, + ], + } + result = _build_ask_user_question_payload(info, "thread-123") + + assert result["question"] == "请确认是否继续?" + assert len(result["options"]) == 2 + assert result["options"][0] == {"label": "确认", "value": "yes"} + assert result["options"][1] == {"label": "取消", "value": "no"} + assert result["source"] == "interrupt" + assert result["thread_id"] == "thread-123" + assert result["multi_select"] is False + assert result["allow_other"] is True + + def test_question_with_source(self): + info = { + "question": "选择一个选项", + "options": ["A", "B", "C"], + "source": "ask_user_question", + } + result = _build_ask_user_question_payload(info, "thread-456") + + assert result["source"] == "ask_user_question" + assert len(result["options"]) == 3 + + def test_multi_select(self): + info = { + "question": "选择多个", + "options": ["A", "B", "C"], + "multi_select": True, + } + result = _build_ask_user_question_payload(info, "thread-789") + + assert result["multi_select"] is True + + def test_disable_allow_other(self): + info = { + "question": "只能选择", + "options": ["A", "B"], + "allow_other": False, + } + result = _build_ask_user_question_payload(info, "thread-000") + + assert result["allow_other"] is False + + def test_with_operation(self): + info = { + "question": "是否执行操作?", + "operation": "删除文件", + "options": [{"label": "批准", "value": "approve"}, {"label": "拒绝", "value": "reject"}], + } + result = _build_ask_user_question_payload(info, "thread-op") + + assert result["operation"] == "删除文件" + + def test_no_options(self): + """测试没有 options 的情况 - 不再自动填充 legacy 选项""" + info = { + "question": "请确认?", + } + result = _build_ask_user_question_payload(info, "thread-no-opt") + + # 不再有默认的 approve/reject 选项 + assert result["options"] == [] + assert result["source"] == "interrupt" + + def test_question_id_generation(self): + """测试 question_id 自动生成""" + info = {"question": "测试?"} + result = _build_ask_user_question_payload(info, "thread-id") + + # 应该生成了 UUID + assert result["question_id"] != "" + assert len(result["question_id"]) > 0 + + +class TestCoerceInterruptPayload: + """测试 _coerce_interrupt_payload 函数""" + + def test_dict_input(self): + info = {"question": "test?", "options": ["a", "b"]} + result = _coerce_interrupt_payload(info) + assert result == info + + def test_string_input(self): + info = "just a string" + result = _coerce_interrupt_payload(info) + assert isinstance(result, dict) + + def test_none_input(self): + result = _coerce_interrupt_payload(None) + assert isinstance(result, dict) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/web/src/composables/useApproval.js b/web/src/composables/useApproval.js index 52a87664..6ecbe87d 100644 --- a/web/src/composables/useApproval.js +++ b/web/src/composables/useApproval.js @@ -18,27 +18,17 @@ const normalizeOptions = (rawOptions) => { .filter(Boolean) } -const toLegacyApprovalOptions = () => [ - { label: '批准 (Recommended)', value: 'approve' }, - { label: '拒绝', value: 'reject' } -] - const extractQuestionPayload = (chunk) => { const interruptInfo = chunk?.interrupt_info || {} - const rawOptions = - chunk?.options || interruptInfo?.options || (interruptInfo?.operation ? toLegacyApprovalOptions() : []) + const rawOptions = chunk?.options || interruptInfo?.options || [] const options = normalizeOptions(rawOptions) const operation = chunk?.operation || interruptInfo?.operation || '' - const source = chunk?.source || interruptInfo?.source || (operation ? 'get_approved_user_goal' : 'interrupt') + const source = chunk?.source || interruptInfo?.source || 'interrupt' const multiSelect = Boolean(chunk?.multi_select ?? interruptInfo?.multi_select ?? false) - let allowOther = Boolean(chunk?.allow_other ?? interruptInfo?.allow_other ?? true) + const allowOther = Boolean(chunk?.allow_other ?? interruptInfo?.allow_other ?? true) const questionId = chunk?.question_id || interruptInfo?.question_id || '' const question = chunk?.question || interruptInfo?.question || '请选择一个选项' - const legacyMode = source === 'get_approved_user_goal' && options.length === 2 - if (source === 'get_approved_user_goal') { - allowOther = false - } return { questionId, @@ -47,17 +37,10 @@ const extractQuestionPayload = (chunk) => { multiSelect, allowOther, source, - operation, - legacyMode + operation } } -const inferApprovedFromAnswer = (answer) => { - if (answer === 'approve') return true - if (answer === 'reject') return false - return null -} - export function useApproval({ getThreadState, resetOnGoingConv, fetchThreadMessages }) { const approvalState = reactive({ showModal: false, @@ -68,7 +51,6 @@ export function useApproval({ getThreadState, resetOnGoingConv, fetchThreadMessa multiSelect: false, allowOther: true, source: '', - legacyMode: false, threadId: null }) @@ -98,15 +80,11 @@ export function useApproval({ getThreadState, resetOnGoingConv, fetchThreadMessa resetOnGoingConv(threadId) threadState.streamAbortController = new AbortController() - const approved = inferApprovedFromAnswer(answer) const requestBody = { thread_id: threadId, answer, config: agentConfigId ? { agent_config_id: agentConfigId } : {} } - if (approved !== null) { - requestBody.approved = approved - } try { const response = await agentApi.resumeAgentChat(currentAgentId, requestBody, { @@ -139,11 +117,6 @@ export function useApproval({ getThreadState, resetOnGoingConv, fetchThreadMessa if (!threadState) return false const payload = extractQuestionPayload(chunk) - if (!payload.options.length) { - payload.options = toLegacyApprovalOptions() - payload.legacyMode = true - payload.allowOther = false - } threadState.isStreaming = false @@ -155,7 +128,6 @@ export function useApproval({ getThreadState, resetOnGoingConv, fetchThreadMessa approvalState.multiSelect = payload.multiSelect approvalState.allowOther = payload.allowOther approvalState.source = payload.source - approvalState.legacyMode = payload.legacyMode approvalState.threadId = chunk.thread_id || threadId fetchThreadMessages({ agentId: currentAgentId, threadId }) @@ -172,7 +144,6 @@ export function useApproval({ getThreadState, resetOnGoingConv, fetchThreadMessa approvalState.multiSelect = false approvalState.allowOther = true approvalState.source = '' - approvalState.legacyMode = false approvalState.threadId = null } From 0dcb9fdb7e6dc1d27bc7d1b0231ebb4ee9ce8992 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Tue, 10 Mar 2026 13:55:19 +0800 Subject: [PATCH 22/24] =?UTF-8?q?docs:=20=E8=A1=A5=E5=85=85=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A1=B9=E7=9B=AE=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vitepress/config.mts | 13 +- docs/latest/advanced/agents-config.md | 347 -------------------- docs/latest/advanced/branding.md | 45 ++- docs/latest/advanced/configuration.md | 261 ++++++++------- docs/latest/advanced/deployment.md | 44 +-- docs/latest/advanced/document-processing.md | 191 +++++------ docs/latest/advanced/misc.md | 87 +++-- docs/latest/advanced/skills-management.md | 86 ----- docs/latest/agents/agents-config.md | 146 ++++++++ docs/latest/agents/context-config.md | 60 ++++ docs/latest/agents/mcp-integration.md | 42 +++ docs/latest/agents/middleware.md | 44 +++ docs/latest/agents/skills-management.md | 283 ++++++++++++++++ docs/latest/agents/tools-system.md | 66 ++++ docs/latest/changelog/contributing.md | 69 ++-- docs/latest/changelog/faq.md | 140 ++++---- docs/latest/intro/evaluation.md | 93 ++++-- docs/latest/intro/knowledge-base.md | 223 +++++++------ docs/latest/intro/model-config.md | 29 ++ docs/latest/intro/project-overview.md | 87 ++++- docs/latest/intro/quick-start.md | 158 ++++----- 21 files changed, 1442 insertions(+), 1072 deletions(-) delete mode 100644 docs/latest/advanced/agents-config.md delete mode 100644 docs/latest/advanced/skills-management.md create mode 100644 docs/latest/agents/agents-config.md create mode 100644 docs/latest/agents/context-config.md create mode 100644 docs/latest/agents/mcp-integration.md create mode 100644 docs/latest/agents/middleware.md create mode 100644 docs/latest/agents/skills-management.md create mode 100644 docs/latest/agents/tools-system.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index e1d4b675..54ca1334 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -38,13 +38,22 @@ export default defineConfig({ { text: '知识库评估', link: '/latest/intro/evaluation' } ] }, + { + text: '智能体开发', + items: [ + { text: '智能体配置', link: '/latest/agents/agents-config' }, + { text: '上下文配置', link: '/latest/agents/context-config' }, + { text: '工具系统', link: '/latest/agents/tools-system' }, + { text: '中间件', link: '/latest/agents/middleware' }, + { text: 'MCP 集成', link: '/latest/agents/mcp-integration' }, + { text: 'Skills 管理', link: '/latest/agents/skills-management' } + ] + }, { text: '高级配置', items: [ { text: '配置系统详解', link: '/latest/advanced/configuration' }, { text: '文档解析', link: '/latest/advanced/document-processing' }, - { text: '智能体', link: '/latest/advanced/agents-config' }, - { text: 'Skills 管理', link: '/latest/advanced/skills-management' }, { text: '品牌自定义', link: '/latest/advanced/branding' }, { text: '其他配置', link: '/latest/advanced/misc' }, { text: '生产部署', link: '/latest/advanced/deployment' } diff --git a/docs/latest/advanced/agents-config.md b/docs/latest/advanced/agents-config.md deleted file mode 100644 index 31ab9955..00000000 --- a/docs/latest/advanced/agents-config.md +++ /dev/null @@ -1,347 +0,0 @@ -# 智能体 - -## 智能体开发 - -系统基于 [LangGraph](https://github.com/langchain-ai/langgraph) 并通过统一的 `AgentManager` 管理所有智能体。`src/agents/__init__.py` 会在启动时遍历 `src/agents` 目录,对每个包含 `__init__.py` 的子包执行自动发现:所有继承 `BaseAgent` 的类都会被注册并立即初始化,因此只要代码落位正确,就不需要再手动登记或修改管理器。 - -仓库预置了若干可直接运行的智能体:`chatbot` 聚焦对话与动态工具调度,`reporter` 演示报告类链路,`deep_agent` 提供深度分析能力。这些目录展示了上下文类、Graph 构造方式、子智能体引用以及中间件组合的范例,新增功能时可以直接复用。 - -### 智能体元数据配置 - -每个智能体可以通过在智能体目录下创建 `metadata.toml` 文件来配置元数据信息。这个文件使用 TOML 格式,包含以下字段: - -- `name`: 智能体显示名称 -- `description`: 智能体功能描述 -- `examples`: 示例问题列表(数组格式) - -例如,`src/agents/chatbot/metadata.toml`: - -<<< @/../src/agents/chatbot/metadata.toml - -**注意**:`metadata.toml` 文件是可选的,如果没有提供,系统将使用智能体类的基本属性。 - -### 创建新的智能体 - -在 `src/agents` 下新建一个包,保持与现有目录一致的结构:放置 Graph 构造逻辑(通常命名为 `graph.py`),并在包内的 `__init__.py` 中暴露主类。 - -智能体类必须继承 `src.agents.common.BaseAgent`,同时实现异步的 `get_graph` 方法来返回编译后的 LangGraph 实例,并配置好 `checkpointer`,否则无法从历史对话中恢复。 - -需要额外上下文字段时,可继承 `BaseContext` 构建自己的配置表单,再把类绑定到 `context_schema`,平台会在 `saves/agents/` 下生成默认配置。 - -案例1 基于MySQL工具,以及自定义 MCP Server 的数据库报表助手。 - -<<< @/../src/agents/reporter/graph.py - -### 工具系统 - -系统提供统一的工具获取函数 `get_tools_from_context(context)`,自动从上下文配置中组装工具列表: - -```python -from src.agents.common.tools import get_tools_from_context - -async def get_graph(self, **kwargs): - context = self.get_context() - tools = await get_tools_from_context(context) - # tools 已包含:基础工具、知识库工具、MCP 工具 -``` - -该函数会自动处理三类工具的组装: -1. **基础工具**: 从 `context.tools` 筛选的内置工具 -2. **知识库工具**: 根据 `context.knowledges` 自动生成检索工具 -3. **MCP 工具**: 根据 `context.mcps` 加载并过滤的 MCP 服务器工具 - -### BaseContext 配置字段 - -`BaseContext` 已内置以下常用配置字段,所有智能体可直接复用: - -| 字段 | 类型 | 说明 | -|------|------|------| -| `model` | str | 使用的 LLM 模型 | -| `system_prompt` | str | 系统提示词 | -| `tools` | list[str] | 启用的内置工具列表 | -| `knowledges` | list[str] | 关联的知识库列表 | -| `mcps` | list[str] | 启用的 MCP 服务器名称 | -| `skills` | list[str] | 关联的 Skills(运行时只读挂载到 `/skills`) | - -```python -from src.agents.common import BaseContext - -@dataclass(kw_only=True) -class MyAgentContext(BaseContext): - # 继承所有 BaseContext 字段 - # 可在此添加智能体特有的额外配置 - custom_field: str = "默认值" -``` - -如需自定义工具选项(如 ReporterAgent 的 MySQL 工具),可覆盖 `tools` 字段的 `options` 元数据: - -```python -from src.agents.common import BaseContext, gen_tool_info -from src.agents.common.toolkits.buildin import calculator, query_knowledge_graph -from src.agents.common.toolkits.buildin.tools import _create_tavily_search -from src.agents.common.toolkits.mysql import get_mysql_tools - -@dataclass(kw_only=True) -class ReporterContext(BaseContext): - tools: Annotated[list[dict], {"__template_metadata__": {"kind": "tools"}}] = field( - default_factory=lambda: [t.name for t in get_mysql_tools()], - metadata={ - "name": "工具", - "options": lambda: gen_tool_info( - [calculator, query_knowledge_graph, _create_tavily_search()] + get_mysql_tools() - ), - "description": "包含内置工具和 MySQL 工具包。", - }, - ) - - def __post_init__(self): - self.mcps = ["mcp-server-chart"] # 默认启用图表 MCP -``` - -智能体实例的生命周期交给管理器处理,会在自动发现时完成初始化并缓存单例,以便快速响应请求。在容器内热重载时,只要保存文件即可触发重新导入;需要强制刷新可调用 `agent_manager.get_agent(, reload=True)`。 - -更多动态工具选择与 MCP 注册的例子,见 `src/agents/chatbot/graph.py` 中的中间件组合。 - -### Skills 只读挂载 - -`BaseContext.skills` 用于声明当前智能体可访问的技能目录(slug 列表)。运行时会将这些目录只读挂载到 `/skills//...`: - -1. 仅显示配置中选中的 skills,未选中的 slug 在运行时不可见。 -2. `/skills` 仅支持读取能力(`ls/read/glob/grep`),写入和编辑会被拒绝。 -3. skills 元数据来自数据库索引,内容目录来自共享存储 `/app/saves/skills`。 - -### 拓展现有智能体 - -智能体保持为 LangGraph 的标准节点组合,因此可以在原有 `graph.py` 中添加节点、条件与消息转换器。复用现成上下文时,只需扩展当前 `context_schema` 的字段;若功能差异较大,可以创建新的上下文类并替换 `context_schema`。 - -对工具、模型或提示语的调整建议封装到中间件或独立函数里,既方便多智能体共用,又能保持 `BaseAgent` 的基础接口稳定。变更提交后无需手动刷新注册表,只要确保包结构未改变,智能体会在热重载中自动更新。 - -### 子智能体与中间件 - -子智能体集中放在 `src/agents/common/subagents` 目录,典型例子是 `calc_agent`,它通过 LangChain 的 `create_agent` 构建计算器能力并以工具暴露给主图。新增子智能体时沿用这一结构:在目录内编写封装函数与 `@tool` 装饰器,导出后即可被任意智能体调用。 - -中间件位于 `src/agents/common/middlewares`,包含上下文感知提示词、模型选择、动态工具加载以及附件注入等实现。如果需要编写新的中间件,请遵循 LangChain 官方文档中对 `AgentMiddleware`、`ModelRequest`、`ModelResponse` 等接口的定义,完成后在该目录的 `__init__.py` 暴露入口,主智能体即可在 `middleware` 列表中引用。 - -#### RuntimeConfigMiddleware - -`RuntimeConfigMiddleware`([runtime_config_middleware.py](https://github.com/xerrors/Yuxi-Know/blob/main/src/agents/common/middlewares/runtime_config_middleware.py))是系统默认的核心中间件之一,负责在每次模型调用前自动注入运行时配置: - -1. **自动注入当前时间**:在 system prompt 开头追加当前时间,格式为 `当前时间:YYYY-MM-DD HH:MM:SS`,确保 LLM 能获取准确的时间上下文。 -2. **动态加载工具**:根据 `context.tools`、`context.knowledges`、`context.mcps` 自动组装可用工具列表。 -3. **模型选择**:根据 `context.model` 加载对应模型配置。 - -如需自定义时间注入逻辑或禁用该行为,可继承该中间件并覆盖 `awrap_model_call` 方法。 - -#### 文件上传中间件 - -文件上传功能通过 `inject_attachment_context` 中间件实现(位于 `src/agents/common/middlewares/attachment_middleware.py`)。该中间件基于 LangChain 1.0 的 `AgentMiddleware` 标准实现,具有以下特点: - -1. **状态扩展**:定义 `AttachmentState` 扩展 `AgentState`,添加可选的 `attachments` 字段 -2. **自动注入**:在模型调用前,从 `request.state` 中读取附件并转换为 `SystemMessage` -3. **向后兼容**:不使用文件上传的智能体不受影响 - -##### 为智能体启用文件上传 - -只需两步: - -**步骤 1:声明能力**(让前端显示上传按钮) - -```python -class MyAgent(BaseAgent): - capabilities = ["file_upload"] -``` - -**步骤 2:添加中间件**(让智能体能够处理附件内容) - -```python -from src.agents.common.middlewares import inject_attachment_context - -async def get_graph(self): - graph = create_agent( - model=load_chat_model("..."), - tools=tools, - middleware=[ - inject_attachment_context, # 添加附件中间件 - context_aware_prompt, # 其他中间件... - # ... - ], - checkpointer=await self._get_checkpointer(), - ) - return graph -``` - -##### 工作流程 - -1. **前端上传**:用户在聊天界面上传文档(txt、md、docx、html) -2. **API 解析**:后端将文档转换为 Markdown 格式并存储到数据库(超过 32k 会被截断) -3. **自动加载**:API 层在调用 agent 前从数据库加载附件数据 -4. **中间件注入**:`inject_attachment_context` 自动将附件内容注入为系统消息 -5. **模型处理**:LLM 接收到附件内容和用户问题,进行综合回答 - -这种设计确保了附件功能的可选性和可扩展性,任何智能体都可以通过添加中间件快速启用文件上传能力。 - -## 内置工具与 MCP 集成 - -系统会根据配置自动组装工具集合,涵盖知识图谱查询、向量检索生成的动态工具、MySQL 只读查询能力、Tavily 搜索以及所有注册的 MCP 工具。 - -MCP (Model Context Protocol) 服务的配置现已全面支持通过系统管理界面或 API 进行动态管理,数据持久化存储在数据库中。`src/services/mcp_service.py` 仅作为核心逻辑层和默认配置的存放处,不再建议直接修改代码来添加服务器。 - -### MCP 服务器管理 - -系统提供了完善的 API (`/system/mcp-servers`) 和管理界面来执行 MCP 服务器的增删改查操作。 - -#### 支持的传输协议 - -系统支持三种 MCP 传输协议: - -1. **SSE (Server-Sent Events)**: 标准的 HTTP SSE 连接 -2. **Streamable HTTP**: 支持流式传输的 HTTP 连接(远程) -3. **Stdio**: 通过标准输入/输出运行本地进程(支持 Python/Node.js 等) - -#### 配置示例 - -以下是通过管理界面添加 MCP 服务器时的常见配置参数示例(对应 API 请求体): - -##### 1. 远程 HTTP/SSE 服务器 - -* **Server Name**: `sequentialthinking` -* **Transport**: `streamable_http` (或 `sse`) -* **URL**: `https://remote.mcpservers.org/sequentialthinking/mcp` - -**特点**: -- 无需本地安装,适合公开可用的 MCP 服务 -- 启动速度快,无需本地依赖 - -##### 2. 使用 npx 运行 Node.js 包 - -* **Server Name**: `mcp-server-chart` -* **Transport**: `stdio` -* **Command**: `npx` -* **Args**: `["-y", "@antv/mcp-server-chart"]` - -**特点**: -- 自动下载并运行 Node.js 包 -- 适合 Node.js 生态的 MCP 服务 - -##### 3. 使用 uvx 运行 Python 包 - -* **Server Name**: `mysql-mcp-server` -* **Transport**: `stdio` -* **Command**: `uvx` -* **Args**: `["mysql_mcp_server"]` -* **Environment Variables**: - ```json - { - "MYSQL_DATABASE": "your_database", - "MYSQL_HOST": "localhost", - "MYSQL_PASSWORD": "your_password", - "MYSQL_PORT": "3306", - "MYSQL_USER": "your_username" - } - ``` - -**特点**: -- 自动管理 Python 虚拟环境和依赖 -- 适合 PyPI 上已发布的 MCP 服务 - -##### 4. 使用 uv 运行本地仓库 - -* **Server Name**: `arxiv-mcp-server` -* **Transport**: `stdio` -* **Command**: `uv` -* **Args**: - ```json - [ - "tool", - "run", - "arxiv-mcp-server", - "--storage-path", "src/agents/mcp_repos/arxiv-mcp-server" - ] - ``` - -**特点**: -- 直接运行本地 git 仓库中的 MCP 服务 -- 支持热重载,适合开发调试 - -### 动态工具加载与管理 - -系统提供统一的 MCP 服务层 (`src/services/mcp_service.py`) 封装所有 MCP 相关操作。 - -#### 1. 智能体获取工具 - -智能体开发时,应使用 `get_enabled_mcp_tools()` 获取工具。该函数会自动根据数据库中的配置,过滤掉被禁用的工具。 - -```python -from src.services.mcp_service import get_enabled_mcp_tools - -# 获取指定服务器的工具(自动过滤掉在管理界面禁用的工具) -tools = await get_enabled_mcp_tools("sequentialthinking") -``` - -#### 2. 工具粒度控制 - -通过管理界面或 API (`PUT /system/mcp-servers/{name}/tools/{tool_name}/toggle`),管理员可以启用或禁用特定的 MCP 工具。禁用后的工具不会出现在 `get_enabled_mcp_tools` 的返回列表中,从而防止智能体调用不需要的能力。 - -#### 3. 默认服务器配置 - -系统首次启动时,会加载 `src/services/mcp_service.py` 中 `_DEFAULT_MCP_SERVERS` 定义的默认服务器(如 `sequentialthinking` 和 `mcp-server-chart`)到数据库中。后续的修改将以数据库为准。 - -### MySQL 数据库 - -在 数据库报表助手(SqlReporterAgent) 中,可以通过配置下面环境变量,让 Agent 能够连接到 MySQL 数据库。并通过执行 SQL 查询,获取数据库中的数据。 - -设置数据库连接时,在 `.env` 中提供以下字段: - -```env -MYSQL_HOST=192.168.1.100 -MYSQL_USER=username -MYSQL_PASSWORD=your_secure_password -MYSQL_DATABASE=database_name -MYSQL_DATABASE_DESCRIPTION=业务主库(可选提示) -MYSQL_PORT=3306 -MYSQL_CHARSET=utf8mb4 -``` - -所有查询限定在只读范围(SELECT、SHOW、DESCRIBE、EXPLAIN),请求会经过表名校验与超时控制,默认限制 60 秒与 100 行输出,并可通过配置调整上限。连接信息会反馈给 LangGraph,智能体可以自动陈述数据库用途并选择更准确的检索策略。详见代码部分 `src/agents/common/toolkits/mysql/` - -### 多模态图片支持 - -系统支持接收图片作为输入,与文本结合形成多模态查询。图片支持的核心特性如下: - -#### 1. 图片上传与处理 -- 通过 `/chat/image/upload` 接口上传图片 -- 自动处理图片格式转换和压缩 -- 返回 base64 编码的图片数据 -- 图片大小限制为 10MB -- 支持的图片格式:JPEG、PNG、WebP、GIF、BMP -- 自动压缩超过 5MB 的图片 - -当发送包含图片的请求时,消息格式为: -```json -{ - "query": "这张图片里有什么?", - "image_content": "", - "config": {}, - "meta": {} -} -``` - -智能体会自动识别多模态消息并将其传递给支持图片的模型。如果模型不支持图片,会自动忽略图片内容,只处理文本部分。系统会将图片转换为符合模型要求的格式(通常是 base64 编码的 JPEG 或 PNG),确保与主流多模态模型兼容。 - -目前仅支持上传单个图片,图片以 base64 编码形式存储在数据库。系统会自动处理图片的格式转换和压缩,并生成缩略图以优化性能。 - -### 图片上传响应格式 - -```json -{ - "success": true, - "image_content": "", - "thumbnail_content": "", - "width": 1024, - "height": 768, - "format": "JPEG", - "mime_type": "image/jpeg" -} -``` - -系统会将图片信息与用户查询一同传递给支持多模态的模型,并自动适配模型要求的格式。 diff --git a/docs/latest/advanced/branding.md b/docs/latest/advanced/branding.md index 6a841cfc..6291a16c 100644 --- a/docs/latest/advanced/branding.md +++ b/docs/latest/advanced/branding.md @@ -1,30 +1,29 @@ # 品牌自定义 -系统支持完整的品牌信息自定义,包括 Logo、组织名称、版权信息等。 +Yuxi-Know 支持完整的品牌自定义,包括 Logo、组织名称、版权信息等,方便企业用户进行品牌定制。 -## 配置方法 +## 品牌信息配置 -### 1. 复制模板文件 +### 步骤 1:复制模板文件 ```bash cp src/config/static/info.template.yaml src/config/static/info.local.yaml ``` -### 2. 编辑品牌信息 +### 步骤 2:编辑品牌信息 -在 `src/config/static/info.local.yaml` 中配置: +在 `src/config/static/info.local.yaml` 中配置你的品牌信息: -<<< @/../src/config/static/info.template.yaml +- 应用名称 +- 组织名称 +- Logo +- 版权信息等 -上述中提到的 ICON 预设了下面这些,如果需要更多的 ICONS,可以手动从 `lucide-vue-next` 中引入。 +### 步骤 3:指定配置文件 -<<< @/../web/src/views/HomeView.vue#icon_mapping{js} +在 `.env` 中指定配置文件路径: -### 3. 环境变量配置 - -在 `.env` 文件中指定配置文件路径: - -```bash +```env YUXI_BRAND_FILE_PATH=src/config/static/info.local.yaml ``` @@ -32,16 +31,13 @@ YUXI_BRAND_FILE_PATH=src/config/static/info.local.yaml `info.local.yaml` > `info.template.yaml`(默认) ::: +### Icon 定制 + +系统预设了多种 Icon,如需更多图标,可以从 `lucide-vue-next` 中引入。 ## 样式定制 -系统配色主要保存在 `web/src/assets/css/base.css` 中: - -- 替换 `--main-*` 相关变量即可改变配色 -- 支持主题色、辅助色等完整定制 -- 实时预览,无需重启服务 - -**主要变量**: +系统支持完整的主题色定制。配置文件位于 `web/src/assets/css/base.css`: ```css :root { @@ -50,11 +46,12 @@ YUXI_BRAND_FILE_PATH=src/config/static/info.local.yaml --main-900: #e6f7ff; /* 色板 */ /* ... 其他色板 */ } - ``` -**此外**,`web/src/stores/theme.js` 中也包含了主题相关的配置(需要修改 `colorPrimary`),可根据需要修改。 +修改配色变量后,界面会实时更新,无需重启服务。 -## 修改首页 +此外,`web/src/stores/theme.js` 中的 `colorPrimary` 也需要同步修改。 -首页提供了一个插槽组件 `web/src/components/ProjectOverview.vue`,可以在该组件中自定义项目介绍,当前为空文件。(借助 AI 编程可以设计出更好看的首页的) +## 首页定制 + +首页的「项目介绍」部分是一个插槽组件,位于 `web/src/components/ProjectOverview.vue`。可以根据需要自定义展示内容。 diff --git a/docs/latest/advanced/configuration.md b/docs/latest/advanced/configuration.md index f77a7089..b358e1f8 100644 --- a/docs/latest/advanced/configuration.md +++ b/docs/latest/advanced/configuration.md @@ -1,34 +1,42 @@ # 配置系统详解 -## 概述 +Yuxi-Know 采用了现代化的配置管理系统,基于 Pydantic BaseModel 和 TOML 格式,提供了类型安全、智能提示和选择性持久化等特性。这套系统既满足了开发者对灵活配置的需求,又保证了运行时的稳定性。 -Yuxi-Know 从 v0.3.x 版本开始采用了全新的配置系统,基于 Pydantic BaseModel 和 TOML 格式,提供了类型安全、智能提示和选择性持久化等现代化特性。 +## 设计理念 -## 架构设计 +传统的配置文件往往存在以下问题:格式不统一、类型安全缺失、难以追踪哪些配置是用户修改过的。Yuxi-Know 的配置系统针对这些问题给出了解决方案: -### 配置层次结构 +- **类型安全**:基于 Pydantic,所有配置项都有明确的类型定义 +- **智能提示**:IDE 可以根据类型定义提供自动补全 +- **选择性持久化**:只保存用户修改过的配置,避免版本冲突 +- **多层覆盖**:代码默认值 → TOML 文件 → 环境变量,按优先级覆盖 + +## 配置层次 + +系统采用三层配置结构,每一层都有不同的优先级和适用场景: ``` -配置系统架构 -├── 默认配置 (代码定义) -│ ├── src/config/static/models.py (模型配置) -│ └── src/config/app.py (应用配置) -├── 用户配置 (TOML 文件) -│ └── saves/config/base.toml (仅保存用户修改) -└── 环境变量 (运行时覆盖) - └── .env 文件 +配置优先级(从低到高) +━━━━━━━━━━━━━━━━━━━━━━━━━━ +环境变量 (.env) → 最高优先级,用于运行时覆盖 +用户配置 (TOML) → 持久化的用户修改 +代码默认值 → 最低优先级,定义在 Python 代码中 +━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` -### 核心组件 +### 各层作用 -#### 1. Config 类 (`src/config/app.py`) +1. **代码默认值**:定义在 `src/config/app.py` 和 `src/config/static/models.py` 中,提供所有配置项的初始值 -主配置类,继承自 Pydantic BaseModel,提供: +2. **用户配置**:保存在 `saves/config/base.toml`,只包含用户实际修改过的配置项。这种设计的好处是:当代码更新添加了新配置项时,不会被用户的旧配置文件覆盖 -- **类型验证**: 自动检查配置项类型 -- **默认值管理**: 内置合理的默认配置 -- **选择性持久化**: 仅保存用户修改的配置项 -- **向后兼容**: 支持旧的字典式访问方式 +3. **环境变量**:适用于容器化部署场景,可以方便地在启动时覆盖任意配置项 + +## 核心组件 + +### 应用配置 + +主配置类 `Config` 继承自 Pydantic BaseModel: ```python class Config(BaseModel): @@ -37,67 +45,13 @@ class Config(BaseModel): enable_content_guard: bool = Field(default=False, description="是否启用内容审查") # 模型配置 - default_model: str = Field(default="default="siliconflow/Pro/deepseek-ai/DeepSeek-V3.2") + default_model: str = Field(default="siliconflow/Pro/deepseek-ai/DeepSeek-V3.2") embed_model: str = Field(default="siliconflow/BAAI/bge-m3") - - # 运行时状态 (不持久化) - model_provider_status: dict[str, bool] = Field(exclude=True) ``` -#### 2. 模型配置类 (`src/config/static/models.py`) +### 模型配置 -定义了三种类型的模型配置: - -- **ChatModelProvider**: 聊天模型提供商 -- **EmbedModelInfo**: 嵌入模型信息 -- **RerankerInfo**: 重排序模型信息 - -```python -class ChatModelProvider(BaseModel): - name: str = Field(..., description="提供商显示名称") - url: str = Field(..., description="提供商文档或模型列表 URL") - base_url: str = Field(..., description="API 基础 URL") - default: str = Field(..., description="默认模型名称") - env: str = Field(..., description="API Key 环境变量名") - models: list[str] = Field(default_factory=list, description="支持的模型列表") -``` - -添加配置: - - -```python -# 1. 在 DEFAULT_CHAT_MODEL_PROVIDERS 中添加 -"new-provider": ChatModelProvider( - name="新提供商", - url="https://provider.com/docs", - base_url="https://api.provider.com/v1", - default="default-model", - env="NEW_PROVIDER_API_KEY", - models=["model1", "model2"], -), - -# 2. 在 .env 中配置 API Key -# NEW_PROVIDER_API_KEY=your_api_key - -# 3. 重启服务或重新加载配置 -``` - -## 配置管理特性 - -系统只会保存用户修改过的配置项: - -```python -# 用户只修改了 enable_reranker -config.enable_reranker = True -config.save() # 只保存 enable_reranker 到 TOML 文件 - -# TOML 文件内容 -# enable_reranker = true -``` - -### 默认模型配置 (`src/config/static/models.py`) - -包含所有支持的模型提供商的默认配置,开发者可以直接修改此文件添加新的模型: +模型配置独立管理,支持多种模型提供商: ```python DEFAULT_CHAT_MODEL_PROVIDERS: dict[str, ChatModelProvider] = { @@ -107,72 +61,102 @@ DEFAULT_CHAT_MODEL_PROVIDERS: dict[str, ChatModelProvider] = { base_url="https://api.siliconflow.cn/v1", default="deepseek-ai/DeepSeek-V3.2", env="SILICONFLOW_API_KEY", - models=[ - "deepseek-ai/DeepSeek-V3.2", - "Qwen/Qwen3-235B-A22B-Instruct-2507", - # ... - ], + models=["deepseek-ai/DeepSeek-V3.2", "Qwen/Qwen3-235B-A22B-Instruct-2507"], ), - # 更多提供商... + # 其他提供商... } ``` -### 用户配置 (`saves/config/base.toml`) +## 使用指南 -只包含用户修改过的配置项,使用 TOML 格式: - -```toml -# 用户只修改了这些配置项 -enable_reranker = true -default_agent_id = "MyCustomAgent" -enable_content_guard = true - -# 模型配置修改 -[model_names.siliconflow] -models = [ - "deepseek-ai/DeepSeek-V3.2", - "custom-model-name", -] -``` - -## 高级配置 - -### 动态配置更新 +### 读取配置 ```python from src.config import config -# 更新配置 +# 访问配置项 +model = config.default_model +reranker_enabled = config.enable_reranker +``` + +### 修改配置 + +```python +from src.config import config + +# 修改配置 config.enable_reranker = True -config.default_agent_id = "CustomAgent" +config.default_model = "custom-model-name" -# 更新模型列表 -config.model_names["siliconflow"].models.append("new-model") - -# 保存配置 +# 保存到 TOML 文件 config.save() - -# 或者只保存特定提供商的模型配置 -config._save_models_to_file("siliconflow") ``` ### 配置验证 ```python -# 验证配置 from src.config import config # 检查模型提供商可用性 for provider, status in config.model_provider_status.items(): - print(f"{provider}: {'✅' if status else '❌'}") + print(f"{provider}: {'可用' if status else '不可用'}") # 获取可用模型列表 -available_models = config.get_model_choices() -available_embed_models = config.get_embed_model_choices() -available_rerankers = config.get_reranker_choices() +models = config.get_model_choices() +embed_models = config.get_embed_model_choices() ``` -### 配置导出 +## 添加新模型提供商 + +需要支持新的模型提供商时,按以下步骤操作: + +### 步骤 1:添加提供商配置 + +在 `src/config/static/models.py` 的 `DEFAULT_CHAT_MODEL_PROVIDERS` 字典中添加新条目: + +```python +"new-provider": ChatModelProvider( + name="新提供商", + url="https://provider.com/docs", + base_url="https://api.provider.com/v1", + default="default-model", + env="NEW_PROVIDER_API_KEY", + models=["model1", "model2"], +), +``` + +### 步骤 2:配置 API Key + +在 `.env` 文件中添加对应的环境变量: + +```env +NEW_PROVIDER_API_KEY=your_api_key_here +``` + +### 步骤 3:重启服务 + +配置完成后,重启服务使配置生效。 + +## 高级特性 + +### 动态更新 + +配置可以动态修改,无需重启服务: + +```python +from src.config import config + +# 更新单个配置项 +config.enable_reranker = True + +# 更新模型列表 +config.model_names["siliconflow"].models.append("new-model") + +# 保存修改 +config.save() +``` + +### 导出配置 ```python # 导出完整配置(包含运行时状态) @@ -183,3 +167,46 @@ user_config = { field: getattr(config, field) for field in config._user_modified_fields } +``` + +### 选择性持久化机制 + +系统会跟踪哪些配置项被修改过: + +```python +# 假设用户只修改了 enable_reranker +config.enable_reranker = True +config.save() # 只保存 enable_reranker 到 TOML 文件 +``` + +生成的 TOML 文件只包含修改过的项: + +```toml +enable_reranker = true +``` + +这种设计的优势: +- 用户升级程序时,新配置项会自动使用默认值 +- 避免配置文件版本冲突 +- 便于查看用户做了哪些自定义修改 + +## 常见问题 + +**Q:新增的配置项没有生效?** + +A:请检查: +1. 配置项名称是否正确拼写 +2. 环境变量是否正确设置(环境变量优先级最高) +3. 是否需要重启服务 + +**Q:如何查看当前所有配置?** + +A:访问 `/api/config` 接口或查看 `config.dump_config()` 的输出。 + +**Q:配置文件格式错误导致启动失败?** + +A:可以删除 `saves/config/base.toml` 文件,让系统重新生成默认配置。 + +--- + +配置系统的设计遵循了「约定优于配置」的原则,大多数情况下使用默认值即可工作。当需要自定义行为时,只需要修改少量配置项即可。理解这套系统的层次结构和优先级,能够帮助你更好地控制和调试应用行为。 diff --git a/docs/latest/advanced/deployment.md b/docs/latest/advanced/deployment.md index 98343435..5f1ae543 100644 --- a/docs/latest/advanced/deployment.md +++ b/docs/latest/advanced/deployment.md @@ -1,55 +1,55 @@ # 生产部署指南 -本指南介绍了如何在生产环境中部署 Yuxi-Know。 +本文档介绍如何在生产环境中部署 Yuxi-Know。 ## 前置要求 -- **Docker Engine** (v24.0+) -- **Docker Compose** (v2.20+) -- **NVIDIA Container Toolkit** (如果在生产环境使用 GPU 服务) +- Docker Engine (v24.0+) +- Docker Compose (v2.20+) +- NVIDIA Container Toolkit(如需使用 GPU 服务) -注意事项: - -1. 生产环境和开发环境最好是两台独立的机器,不然会存在端口和资源的冲突问题。 -2. 虽然名为“生产环境”,但实际上只是做了一些基本的配置而已,真要上线业务,需要根据实际情况进行调整。 -3. 前端有个**调试面板**,长按侧边栏触发,生产环境不建议开启。 +::: warning 注意事项 +1. 生产环境和开发环境建议使用不同的机器,避免端口和资源冲突 +2. 虽然名为「生产环境」,但这只是基本配置,真正上线需要根据实际情况调整 +3. 前端有调试面板(长按侧边栏触发),生产环境建议关闭 +::: ## 部署步骤 -### 1. 配置环境变量 +### 1. 准备配置文件 -为了避免与开发环境的冲突,建议在生产环境中使用 `.env.prod` 文件。请确保你已经从模板创建了该文件并填写了必要的密钥。 +为避免与开发环境冲突,生产环境建议使用 `.env.prod` 文件: ```bash cp .env.template .env.prod ``` -编辑 `.env.prod` 文件,设置强密码并配置必要的 API 密钥: +编辑 `.env.prod`,设置强密码和必要的 API 密钥: -- `NEO4J_PASSWORD`: 修改默认密码 -- `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY`: 修改默认密钥 +- `NEO4J_PASSWORD`:修改默认密码 +- `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY`:修改默认密钥 - `SILICONFLOW_API_KEY` 等模型密钥 ### 2. 启动服务 -使用 `docker-compose.prod.yml` 文件启动生产环境: +使用生产环境配置文件启动: ```bash -# 仅启动核心服务 (CPU 模式) +# 仅启动核心服务(CPU 模式) docker compose -f docker-compose.prod.yml up -d --build -# 启动所有服务 (包含 GPU OCR 服务) +# 启动所有服务(包含 GPU OCR) docker compose -f docker-compose.prod.yml --profile all up -d --build ``` ### 3. 验证部署 -- **Web 访问**: `http://localhost` (直接通过 80 端口访问,无需 :5173) -- **API 健康检查**: `curl http://localhost/api/system/health` +- Web 访问:http://localhost(直接通过 80 端口) +- API 健康检查:`curl http://localhost/api/system/health` ## 维护与更新 -### 更新代码并重新部署 +### 更新代码 ```bash # 拉取最新代码 @@ -62,9 +62,9 @@ docker compose -f docker-compose.prod.yml up -d --build ### 查看日志 ```bash -# 查看 API 日志 +# API 日志 docker logs -f api-prod -# 查看 Nginx 访问日志 +# Nginx 访问日志 docker logs -f web-prod ``` diff --git a/docs/latest/advanced/document-processing.md b/docs/latest/advanced/document-processing.md index a84740d7..227b8827 100644 --- a/docs/latest/advanced/document-processing.md +++ b/docs/latest/advanced/document-processing.md @@ -1,159 +1,126 @@ # 文档处理与 OCR -系统提供 5 种文档处理选项: - -- **RapidOCR**: CPU 友好,无需 GPU,适合基础文字识别 -- **MinerU**: 本地化高精度 VLM 解析,适合复杂 PDF 和表格文档 -- **MinerU Official**: 官方云服务 API,无需本地部署,开箱即用 -- **PP-StructureV3**: 结构化解析,适合表格、票据等特殊格式 -- **DeepSeek OCR**: 基于 SiliconFlow API 的 DeepSeek OCR OCR 服务 +Yuxi-Know 支持多种文档格式的智能解析,从简单的文本文件到复杂的 PDF 文档,都能自动提取内容并转换为可检索的格式。 ## 支持的文件类型 -### 常规文档格式 -- **文本文档**: `.txt`, `.md`, `.html`, `.htm` -- **Word 文档**: `.docx` -- **PDF 文档**: `.pdf` -- **电子表格**: `.csv`, `.xls`, `.xlsx` -- **JSON 数据**: `.json` +### 常规文档 -::: tip 图片显示 -文档中的图片会自动上传到对象存储并替换为可访问的 URL。但是如果想要在外部正常显示图片,需要配置 `HOST_IP` 环境变量,将其设置为您的服务器 IP 地址。 -::: +| 类型 | 格式 | 说明 | +|------|------|------| +| 文本 | .txt, .md, .html | 直接提取内容 | +| Word | .docx | 保留格式和结构 | +| PDF | .pdf | 支持文本和图片 PDF | +| 表格 | .csv, .xls, .xlsx | 识别表格结构 | +| JSON | .json | 结构化数据 | -### 图像格式(需要 OCR) -- **常见图片**: `.jpg`, `.jpeg`, `.png`, `.bmp`, `.tiff`, `.tif`, `.gif`, `.webp` +### 图片文件 -### ZIP 压缩包 -- **ZIP 文档**: `.zip` - 支持包含 Markdown 文件和图片的压缩包 - - 自动提取和处理 ZIP 包中的 `.md` 文件 - - 自动处理 ZIP 包中的图片文件并上传到对象存储(MINIO) - - 图片链接会自动替换为可访问的 URL - - 优先处理名为 `full.md` 的文件,否则使用第一个 `.md` 文件 - - 支持图片目录的智能识别(`images/`、`../images/` 等) +对于图片文件,需要启用 OCR 才能提取文字: +- .jpg, .jpeg, .png, .bmp, .tiff, .tif, .gif, .webp -### URL 网页内容 -- **网页链接**: `http://` 或 `https://` - - 自动抓取网页 HTML 内容并转换为 Markdown - - **白名单机制**: 出于安全考虑,必须配置环境变量 `YUXI_URL_WHITELIST` 才能使用此功能 - - **内网保护**: 默认禁止抓取私有 IP 地址(如 127.0.0.1, 192.168.x.x) - - **去重机制**: 自动检测 URL 是否已存在,以及内容 Hash 是否重复 +### 压缩包 + +支持上传 ZIP 压缩包,系统会: +- 自动提取并处理其中的 Markdown 文件 +- 处理图片并上传到对象存储 +- 智能识别 `full.md` 或第一个 `.md` 文件 + +### 网页内容 + +支持通过 URL 直接抓取网页内容: + +1. 配置 `YUXI_URL_WHITELIST` 环境变量启用白名单机制 +2. 系统自动将 HTML 转换为 Markdown +3. 内置去重机制,避免重复抓取 ::: tip URL 白名单配置 -在 `.env` 文件中配置允许抓取的域名列表,用逗号分隔。支持通配符。 -例如:`YUXI_URL_WHITELIST=github.com,*.wikipedia.org,docs.python.org` +示例:`YUXI_URL_WHITELIST=github.com,*.wikipedia.org,docs.python.org` ::: +## OCR 方案选择 +系统提供多种 OCR 方案,适用于不同场景: + +### 方案对比 + +| 方案 | 适用场景 | 硬件要求 | 特点 | +|------|----------|----------|------| +| RapidOCR | 基础文字识别 | CPU | 免费开源,速度快 | +| MinerU | 复杂 PDF、表格 | GPU | 精度高,版面分析好 | +| MinerU Official | 复杂文档 | 无 | 官方云服务,开箱即用 | +| PP-StructureV3 | 表格、票据 | GPU | 专业版面解析 | +| DeepSeek OCR | 智能理解 | 无 | 云端服务,Markdown 输出 | + +### 选择建议 + +- **个人使用或 CPU 环境**:选择 RapidOCR,免费且资源占用低 +- **高精度需求**:选择 MinerU(需要 GPU)或 MinerU Official +- **表格密集型文档**:选择 PP-StructureV3 +- **简单云服务**:选择 DeepSeek OCR ## 快速配置 -### 1. 基础 OCR (RapidOCR) +### RapidOCR(推荐入门) ```bash # 下载模型 hf download SWHL/RapidOCR --local-dir ./models/SWHL/RapidOCR +# 配置环境变量 +MODEL_DIR=./models + # 启动服务 docker compose up -d api ``` -需要确保 `MODEL_DIR` 环境变量指向 RapidOCR 上层目录,例如 `./models`。 +### MinerU(高精度) -### 2. 高精度 OCR (MinerU) +```env +# .env 配置 +MINERU_VL_SERVER=http://localhost:30000 +MINERU_API_URI=http://localhost:30001 -需要在 `.env` 文件中配置: - -```bash -MINERU_VL_SERVER=http://localhost:30000 # 对应 docker compose 中的 mineru-vllm-server 服务 -MINERU_API_URI=http://localhost:30001 # 对应 docker compose 中的 mineru-api 服务 -``` - -然后启动相关服务 - -```bash -# 需要 GPU,启动 MinerU 服务 +# 启动服务(需要 GPU) docker compose up mineru-vllm-server mineru-api -d - -# 启动主服务 -docker compose up api -d ``` -::: tip 处理超时 -文档解析超时时间默认 1800 秒,可通过 `MINERU_TIMEOUT` 环境变量调整。 -::: +### MinerU Official(云服务) +```env +# .env 配置 +MINERU_API_KEY=your-api-key-here +``` -### 3. 官方云服务 (MinerU Official) +从 [MinerU 官网](https://mineru.net) 获取 API 密钥。 -API 密钥可以从 [MinerU 官网](https://mineru.net) 申请。 - -然后在 `.env` 文件中添加 +### PP-StructureV3(结构化) ```bash -# 设置 API 密钥环境变量 -MINERU_API_KEY="your-api-key-here" +# 启动服务(需要 GPU) +docker compose up paddlex -d ``` -然后使用 `docker compose up api -d` 重启后端服务。 +### DeepSeek OCR(简单云服务) -### 4. 结构化解析 (PP-StructureV3) - -```bash -# 需要 GPU,启动 PP-StructureV3 服务 -docker compose up -d paddlex - -# 启动主服务 -docker compose up -d api +```env +# .env 配置(使用已有的 SiliconFlow 密钥) +SILICONFLOW_API_KEY=your-api-key-here ``` -### 5. DeepSeek OCR (SiliconFlow) +## 图片显示配置 -DeepSeek OCR 基于 SiliconFlow API,提供智能文档理解和 Markdown 格式输出。 +上传文档中的图片需要正确配置才能在外部显示: -API 密钥可以从 [SiliconFlow](https://cloud.siliconflow.cn/i/Eo5yTHGJ) 申请。 +在 `.env` 中设置服务器 IP: -然后在 `.env` 文件中添加: - -```bash -# 设置 SiliconFlow API 密钥 -SILICONFLOW_API_KEY="your-api-key-here" +```env +HOST_IP=your_server_ip ``` -重启后端服务即可使用: +## 注意事项 -```bash -docker compose restart api -``` - -注:当前还不支持保存其中的图片信息,mineru 当前版本已支持。 - -## 处理器选择 - -| 处理器 | 适用场景 | 硬件要求 | 特点 | -|--------|----------|------------|------| -| **RapidOCR** | 基础文字识别 | CPU | 速度快,资源占用低 | -| **MinerU** | 复杂 PDF、表格、公式 | GPU | 精度高,版面分析好 | -| **MinerU Official** | 复杂文档解析(云服务) | 无特殊要求 | 官方云服务,开箱即用,有 API 配额 | -| **PP-StructureV3** | 表格、票据、结构化文档 | GPU | 专业版面解析 | -| **DeepSeek OCR** | 智能文档理解和 Markdown 输出 | 无特殊要求 | 云端服务 | - -## 参数说明 - -### enable_ocr 选项 - -对应网页中的 `使用 OCR` 选项 - -- `disable`: 不启用 OCR(PDF 按文本提取,图片**必须选择 OCR 方式**) -- `onnx_rapid_ocr`: RapidOCR 处理 -- `mineru_ocr`: MinerU HTTP API 处理 -- `mineru_official`: MinerU 官方云服务 API 处理 -- `paddlex_ocr`: PP-StructureV3 处理 -- `deepseek_ocr`: DeepSeek OCR(SiliconFlow API)处理 - -### 注意事项 -- **图片文件必须启用 OCR**,否则无法提取内容 -- MinerU 和 PP-StructureV3 需要 GPU 支持 -- MinerU Official 需要设置 `MINERU_API_KEY` 环境变量 -- DeepSeek OCR 需要设置 `SILICONFLOW_API_KEY` 环境变量 -- RapidOCR 适合 CPU 环境和基础识别需求 \ No newline at end of file +1. **图片文件必须启用 OCR**:否则无法提取内容 +2. **GPU 要求**:MinerU 和 PP-StructureV3 需要 GPU 支持 +3. **API 密钥**:部分服务需要额外的 API 密钥配置 +4. **超时处理**:复杂文档解析可能耗时较长,可通过 `MINERU_TIMEOUT` 环境变量调整超时时间 diff --git a/docs/latest/advanced/misc.md b/docs/latest/advanced/misc.md index 333ab97e..85b4df80 100644 --- a/docs/latest/advanced/misc.md +++ b/docs/latest/advanced/misc.md @@ -1,54 +1,83 @@ # 其他配置 +本文档介绍 Yuxi-Know 的其他配置选项,包括内容安全、网页搜索和服务端口等。 + ## 内容安全 -系统内置内容审查机制(默认是关闭状态),保障服务内容的合规性。目前配置了关键词过滤以及 LLM 对内容进行审查。管理员可在 `设置` → `基本设置` 页面中进行配置并选择安全模型。 +系统内置内容审查机制,帮助保障服务内容的合规性。 -检测流程为,接收到用户输入之后,就对用户的输入进行检测是否合规,同时在流式传输的过程中进行实时检测(仅关键词)。当流式输出结束之后,则开始检测整个内容。 -**注意**,使用 LLM 检测虽然可以大大缓解提示词注入带来的问题,但也会在用户交互上带来延迟影响,需要考虑是否启用。 +### 启用方式 -对于关键词检测,敏感词词库位于 `src/config/static/bad_keywords.txt` 文件,每行一个关键词,实时生效,无需重启服务。 +在「系统设置」→「基本设置」页面中配置,可选择启用关键词过滤和 LLM 内容审查。 -对于 LLM 检测,Prompt 可以看到 `src/plugins/guard.py`: +### 检测流程 -<<< @/../src/plugins/guard.py#guard_prompt +系统会在以下时机进行检测: + +1. **用户输入检测**:接收到用户消息后立即检测 +2. **流式输出检测**:实时检测输出的关键词(仅关键词模式) +3. **输出完成检测**:流式输出结束后进行全面检测 + +### 检测模式 + +**关键词检测** + +敏感词库位于 `src/config/static/bad_keywords.txt`,每行一个关键词。修改后实时生效,无需重启服务。 + +**LLM 检测** + +使用大模型对内容进行审查,可以更好地识别提示词注入等复杂问题,但会增加响应延迟。 + +::: warning 性能考虑 +LLM 检测会增加用户交互的延迟,请根据实际需求选择是否启用。 +::: ## 网页搜索 -系统内置了基于 Tavily 的联网搜索能力,配置完成后,大模型会自动在需要时调用对应的工具,为回答提供实时网页信息。 +系统集成了 Tavily 联网搜索能力,让大模型能够获取实时网页信息。 +### 配置步骤 -1. 前往 [Tavily 官网](https://app.tavily.com/) 注册并在控制台创建 API Key。 -2. 在项目根目录的 `.env`(或 `docker-compose.yml` 中的对应环境变量段)写入: +1. 访问 [Tavily 官网](https://app.tavily.com/) 注册并创建 API Key +2. 在 `.env` 文件中添加: ```env TAVILY_API_KEY=sk-xxxxxxxxxxxxxxxx ``` -3. 重新加载服务使密钥生效,推荐执行: +3. 重启服务: ```bash docker compose up -d api-dev web-dev ``` - 若服务已运行,则使用 `docker compose restart api-dev` 即可。 -完成以上步骤后,在智能体的工具配置区域即可看到这个工具,展示 Tavily 返回的实时结果。若需要关闭该能力,删除或清空 `TAVILY_API_KEY` 后再次重启服务即可。 +### 使用方式 + +配置完成后,在智能体的工具配置区域会看到 Tavily 搜索工具。模型会自动判断何时需要调用搜索来获取最新信息。 + +如需关闭,删除或清空 `TAVILY_API_KEY` 后重启服务即可。 ## 服务端口 -系统使用多个端口提供不同服务,以下是完整的端口映射: +系统各服务通过以下端口提供访问: -| 端口 | 服务 | 容器名称 | 说明 | -|------|------|----------|------| -| **5173** | Web 前端 | web-dev | 用户界面 | -| **5050** | API 后端 | api-dev | 核心服务 | -| **7474/7687** | Neo4j | graph | 图数据库 | -| **9000/9001** | MinIO | milvus-minio | 对象存储 | -| **19530/9091** | Milvus | milvus | 向量数据库 | -| **5432** | postgres | postgres | PostgreSQL 数据库 | -| **30000** | MinerU | mineru | PDF 解析(可选)| -| **8080** | PP-StructureV3 | paddlex-ocr | OCR 服务(可选)| -| **8081** | vLLM | - | 本地推理(可选)| +| 端口 | 服务 | 说明 | +|------|------|------| +| 5173 | Web 前端 | 用户界面 | +| 5050 | API 后端 | 核心服务接口 | +| 7474 | Neo4j HTTP | 图数据库管理界面 | +| 7687 | Neo4j Bolt | 图数据库连接 | +| 9000/9001 | MinIO | 对象存储 | +| 19530/9091 | Milvus | 向量数据库 | +| 5432 | PostgreSQL | 业务数据库 | -::: tip 端口访问 -- Web 界面: `http://localhost:5173` -- API 文档: `http://localhost:5050/docs` -- Neo4j 管理: `http://localhost:7474` -::: +### 可选服务端口 + +| 端口 | 服务 | 说明 | +|------|------|------| +| 30000 | MinerU | PDF 解析服务 | +| 8080 | PP-StructureV3 | OCR 服务 | +| 8081 | vLLM | 本地推理服务 | + +### 快速访问 + +- Web 界面:http://localhost:5173 +- API 文档:http://localhost:5050/docs +- Neo4j 管理:http://localhost:7474 diff --git a/docs/latest/advanced/skills-management.md b/docs/latest/advanced/skills-management.md deleted file mode 100644 index 64b58307..00000000 --- a/docs/latest/advanced/skills-management.md +++ /dev/null @@ -1,86 +0,0 @@ -# Skills 管理 - -Skills 管理模块用于集中维护可供 Agent 只读引用的技能包。 -本期采用“文件系统存内容,数据库存索引”模式: - -1. 技能目录存储在 `/app/saves/skills`(本地 `save_dir/skills`)。 -2. 技能元数据(slug/name/description/dir_path)存储在 `skills` 表。 -3. Agent 配置通过 `context.skills` 选择技能,运行时挂载到 `/skills` 且只读。 - -## 权限与入口 - -1. 系统设置中新增 `Skills 管理` 页签(仅 `superadmin` 可见)。 -2. `admin` 仅可调用列表接口(用于 Agent 配置选择 skills)。 -3. `user` 无 skills 管理权限。 - -## 导入规范(ZIP) - -1. 单包单技能,且必须包含一个 `SKILL.md`。 -2. `SKILL.md` 必须包含 frontmatter,且 `name`、`description` 必填。 -3. `name` 需满足 slug 规则:小写字母/数字/短横线。 -4. 导入时执行路径安全校验,拒绝绝对路径与 `..` 路径穿越。 -5. slug 冲突时自动追加 `-v2/-v3...`,并自动改写 `SKILL.md` 中 `name` 为最终 slug。 -6. 导入采用临时目录 + 原子替换,避免半成品落盘。 - -## 在线管理能力 - -1. Skills 列表:来自数据库,避免全量目录扫描。 -2. 目录树:按原生目录结构展示。 -3. 文件级 CRUD:支持新建文件/目录、编辑文本文件、删除文件/目录。 -4. 文件编辑仅允许文本类型(如 md/py/js/ts/json/yaml/toml/txt 等)。 -5. `SKILL.md` 保存后会重新解析,并同步更新数据库中的 `name/description`。 -6. 支持导出单个 skill 为 ZIP。 -7. 删除 skill 时会同时删除目录与数据库记录(硬删除)。 - -## Agent 运行时行为 - -1. `context.skills` 用于配置技能 slug 列表。 -2. 运行时按会话构建 `SkillResolver` 快照(同一会话首次构建,后续复用)。 -3. 运行时仅暴露快照中的可见 skills 到 `/skills//...`。 -4. `/skills` 路径只读,不允许写入、编辑、上传。 -5. 同会话内若 `context.skills` 变化会触发快照重建。 -6. 后台修改 skills 内容后,已有会话不会自动刷新,需新会话或调整 `context.skills` 才生效。 - -## 依赖类型说明 - -每个 skill 支持三类依赖,均在 Skills 管理页维护: - -1. `tool_dependencies`:该 skill 需要的内置工具名列表。 -2. `mcp_dependencies`:该 skill 需要的 MCP 服务器名列表。 -3. `skill_dependencies`:该 skill 依赖的其他 skill slug 列表。 - -约束与语义: - -1. 依赖在保存时做合法性校验,不允许引用不存在的工具/MCP/skill。 -2. `skill_dependencies` 不允许包含自身。 -3. `skill_dependencies` 按递归闭包生效,自动去重、去环、保序。 - -## 渐进式加载流程 - -系统不会在会话开始时一次性加载全部依赖,而是按阶段渐进加载: - -### 阶段 1:会话启动前(构建 skill 可见集) - -1. 读取 `context.skills` 作为用户显式选择的 skills(selected)。 -2. `SkillResolver` 递归展开 `skill_dependencies`,得到 `visible_skills`(selected + 依赖闭包)。 -3. 把快照写入 `runtime.context.skill_session_snapshot`。 -4. 基于 `visible_skills` 构建 skills prompt 段,并在 `abefore_agent` 预拼接到 `system_prompt`。 -5. `/skills` 只挂载 `visible_skills`,所以被依赖 skill 从会话首轮起即可被读取。 - -结论:`skill_dependencies` 是“会话启动即生效”的。 - -### 阶段 2:技能激活时(按需激活) - -1. Agent 通过 `read_file` 读取 `/skills//SKILL.md` 时,视为激活该 skill。 -2. 仅当 `` 在 `skill_session_snapshot.visible_skills` 内,激活才被接受。 -3. 激活结果写入 `activated_skills`(去重保序)。 - -结论:只有“真正被读取并使用”的 skill 才会进入后续依赖注入计算。 - -### 阶段 3:后续模型轮次(注入工具与 MCP 依赖) - -1. 在 `awrap_model_call` 中,基于 `activated_skills` 计算依赖闭包。 -2. 聚合闭包内 skill 的 `tool_dependencies` 与 `mcp_dependencies`。 -3. 仅把这些依赖工具/MCP 合并进本轮可用工具集。 - -结论:`tool_dependencies` 与 `mcp_dependencies` 是“激活后按需加载”的,不会在会话首轮全量注入。 diff --git a/docs/latest/agents/agents-config.md b/docs/latest/agents/agents-config.md new file mode 100644 index 00000000..5bc91ed4 --- /dev/null +++ b/docs/latest/agents/agents-config.md @@ -0,0 +1,146 @@ +# 智能体开发指南 + +Yuxi-Know 的智能体系统基于 LangGraph 构建,提供了灵活而强大的 Agent 开发能力。通过统一的 `AgentManager`,系统能够自动发现和管理所有智能体,让开发者能够专注于业务逻辑的实现。 + +## 智能体架构 + +### 核心概念 + +系统的智能体架构围绕几个核心组件展开: + +- **BaseAgent**:所有智能体的基类,定义了统一的接口规范 +- **AgentContext**:智能体的配置上下文,包含模型、提示词、工具等配置 +- **Graph**:LangGraph 图结构,定义智能体的执行流程 +- **Middleware**:中间件系统,用于扩展和定制智能体行为 + +### 自动发现机制 + +智能体采用自动发现模式。在 `src/agents/__init__.py` 中,系统会遍历 `src/agents` 目录,自动注册所有继承自 `BaseAgent` 的类。这意味着开发者只需要按照规范编写代码,智能体就会自动被系统识别,无需手动配置。 + +仓库预置了几个可以直接使用的智能体示例: + +- **chatbot**:通用对话智能体,支持动态工具调度 +- **reporter**:报表生成智能体,演示多工具协作 +- **deep_agent**:深度分析智能体,支持复杂推理任务 + +这些示例展示了如何组织代码结构、如何定义上下文、如何组合中间件,新增智能体时可以作为参考。 + +## 创建自定义智能体 + +### 目录结构 + +在 `src/agents` 目录下创建新的智能体包,建议保持以下结构: + +``` +src/agents/ +└── my_agent/ + ├── __init__.py # 暴露主类 + ├── graph.py # Graph 构造逻辑 + └── metadata.toml # 元数据配置(可选) +``` + +### 基本实现 + +智能体类需要继承 `BaseAgent` 并实现异步的 `get_graph` 方法: + +```python +from src.agents.common import BaseAgent +from langgraph.prebuilt import create_agent + +class MyAgent(BaseAgent): + async def get_graph(self, **kwargs): + # 获取配置上下文 + context = self.get_context() + + # 获取工具列表 + tools = await get_tools_from_context(context) + + # 构建 LangGraph 图 + graph = create_agent( + model=load_chat_model(context.model), + tools=tools, + checkpointer=await self._get_checkpointer(), + ) + + return graph +``` + +### 能力配置 + +`capabilities` 属性用于声明智能体的前端能力,控制 UI 组件的显示: + +```python +class MyAgent(BaseAgent): + capabilities = ["file_upload", "files", "todo"] # 支持文件上传、文件管理、待办事项 +``` + +**可用能力:** + +| capability | 说明 | 前端效果 | +|------------|------|----------| +| `file_upload` | 文件上传 | 显示上传按钮 | +| `files` | 文件管理 | 显示文件管理面板 | +| `todo` | 待办事项 | 显示待办组件 | + +**示例:** + +```python +# 只需要文件上传能力 +capabilities = ["file_upload"] + +# 需要文件上传和待办事项 +capabilities = ["file_upload", "todo"] + +# 全部能力 +capabilities = ["file_upload", "files", "todo"] +``` + +注意:即使启用了能力,也需要在中间件中正确配置对应的处理逻辑,功能才能正常工作。例如启用 `file_upload` 需要配合 `inject_attachment_context` 中间件。 + +### 配置文件 + +可以通过 `metadata.toml` 定义智能体的元数据: + +```toml +name = "我的智能体" +description = "这是一个示例智能体" +examples = [ + "帮我写一首诗", + "解释一下量子计算", +] +``` + +这些信息会在前端界面展示,帮助用户了解每个智能体的用途。 + +## 相关主题 + +- [上下文配置](./context-config.md) - BaseContext 和自定义配置 +- [工具系统](./tools-system.md) - 工具获取机制和 Skills 集成 +- [中间件系统](./middleware.md) - 中间件开发与使用 +- [MCP 集成](./mcp-integration.md) - MCP 服务器配置 + +## 开发建议 + +### 代码组织 + +- 将智能体的核心逻辑放在 `graph.py` 中 +- 复杂的工具逻辑单独放在 `toolkits` 目录下 +- 共享的组件放在 `common` 目录下 + +### 热重载 + +在容器环境中,修改代码后会自动触发热重载。如果需要强制刷新,可以调用: + +```python +agent_manager.get_agent(, reload=True) +``` + +### 调试技巧 + +1. 使用前端的「调试面板」查看详细的请求和响应 +2. 查看后端日志:`docker logs api-dev -f` +3. 利用 LangGraph 的可视化能力理解图结构 + +--- + +智能体系统的设计目标是让开发者能够快速构建和迭代 AI 应用。通过本文档介绍的概念和示例,你应该能够掌握创建自定义智能体的核心方法。遇到问题时,建议先参考预置智能体的实现,它们涵盖了大多数常见场景。 diff --git a/docs/latest/agents/context-config.md b/docs/latest/agents/context-config.md new file mode 100644 index 00000000..abbef9e6 --- /dev/null +++ b/docs/latest/agents/context-config.md @@ -0,0 +1,60 @@ +# 上下文配置 + +`BaseContext` 是智能体的配置基类,封装了常用的配置字段,定义了智能体的运行时行为。 + +## BaseContext 详解 + +```python +from src.agents.common import BaseContext +from dataclasses import dataclass + +@dataclass(kw_only=True) +class MyAgentContext(BaseContext): + # 继承以下字段: + # model: str - 使用的语言模型 + # system_prompt: str - 系统提示词 + # tools: list[str] - 启用的工具列表 + # knowledges: list[str] - 关联的知识库 + # mcps: list[str] - 启用的 MCP 服务器 + # skills: list[str] - 关联的 Skills + + # 可在此添加自定义字段 + custom_field: str = "默认值" +``` + +### 字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| model | str | 使用的语言模型 | +| system_prompt | str | 系统提示词 | +| tools | list[str] | 启用的内置工具列表 | +| knowledges | list[str] | 关联的知识库 | +| mcps | list[str] | 启用的 MCP 服务器 | +| skills | list[str] | 关联的 Skills | + +## 自定义工具选项 + +有时需要自定义工具选项,比如 ReporterAgent 需要包含 MySQL 工具: + +```python +from src.agents.common import BaseContext, gen_tool_info +from src.agents.common.toolkits.buildin import calculator, query_knowledge_graph +from src.agents.common.toolkits.mysql import get_mysql_tools + +@dataclass(kw_only=True) +class ReporterContext(BaseContext): + tools: Annotated[list[dict], {"__template_metadata__": {"kind": "tools"}}] = field( + default_factory=lambda: [t.name for t in get_mysql_tools()], + metadata={ + "name": "工具", + "options": lambda: gen_tool_info( + [calculator, query_knowledge_graph, _create_tavily_search()] + get_mysql_tools() + ), + "description": "包含内置工具和 MySQL 工具包。", + }, + ) + + def __post_init__(self): + self.mcps = ["mcp-server-chart"] # 默认启用图表 MCP +``` diff --git a/docs/latest/agents/mcp-integration.md b/docs/latest/agents/mcp-integration.md new file mode 100644 index 00000000..298d0049 --- /dev/null +++ b/docs/latest/agents/mcp-integration.md @@ -0,0 +1,42 @@ +# MCP 集成 + +MCP(Model Context Protocol)是扩展智能体能力的重要方式。系统支持通过管理界面动态配置 MCP 服务器,无需修改代码。 + +## 支持的传输协议 + +| 协议 | 说明 | 适用场景 | +|------|------|----------| +| Streamable HTTP | 流式 HTTP 连接 | 远程 MCP 服务 | +| SSE | Server-Sent Events | 标准 HTTP 长连接 | +| Stdio | 标准输入输出 | 本地进程 | + +## 配置示例 + +### 远程 MCP 服务 + +```json +{ + "name": "sequentialthinking", + "transport": "streamable_http", + "url": "https://remote.mcpservers.org/sequentialthinking/mcp" +} +``` + +### 本地 Python 进程 + +```json +{ + "name": "mysql-mcp-server", + "transport": "stdio", + "command": "uvx", + "args": ["mysql_mcp_server"], + "env": { + "MYSQL_HOST": "localhost", + "MYSQL_DATABASE": "your_database" + } +} +``` + +## 工具管理 + +MCP 工具支持粒度控制:管理员可以单独启用或禁用某个 MCP 服务器下的特定工具,实现精细化的权限管理。 diff --git a/docs/latest/agents/middleware.md b/docs/latest/agents/middleware.md new file mode 100644 index 00000000..5027bc85 --- /dev/null +++ b/docs/latest/agents/middleware.md @@ -0,0 +1,44 @@ +# 中间件系统 + +中间件是扩展智能体行为的重要机制。系统基于 LangChain 1.0 的中间件标准,支持在关键节点插入自定义逻辑。 + +## 核心中间件 + +### RuntimeConfigMiddleware + +这是系统的默认中间件,负责在每次模型调用前注入运行时配置: + +- 自动注入当前时间到系统提示词 +- 根据配置动态加载工具列表 +- 处理模型选择和加载 + +### inject_attachment_context + +支持文件上传功能的中间件。如果智能体需要处理用户上传的文档,可以启用此中间件: + +```python +from src.agents.common.middlewares import inject_attachment_context + +async def get_graph(self): + graph = create_agent( + model=load_chat_model("..."), + tools=tools, + middleware=[ + inject_attachment_context, # 启用附件处理 + context_aware_prompt, # 其他中间件 + ], + checkpointer=await self._get_checkpointer(), + ) + return graph +``` + +### 启用文件上传 + +启用文件上传能力需要两步: + +1. 在智能体类中声明 `capabilities = ["file_upload"]` +2. 添加上述中间件 + +## 自定义中间件 + +新增中间件时,将其放入 `src/agents/common/middlewares` 目录,然后在智能体的 `middleware` 列表中引用即可。 diff --git a/docs/latest/agents/skills-management.md b/docs/latest/agents/skills-management.md new file mode 100644 index 00000000..7b993331 --- /dev/null +++ b/docs/latest/agents/skills-management.md @@ -0,0 +1,283 @@ +# Skills 管理系统 + +Skills 是 Yuxi-Know 系统中用于扩展 Agent 能力的重要机制。通过 Skills,开发者可以将特定的工具、提示词模板或领域知识打包成可复用的技能包,让 Agent 在对话过程中能够调用这些额外能力。 + +## 为什么需要 Skills + +在实际业务场景中,我们常常会遇到一些特定的需求:比如需要 Agent 能够查询特定的 API、调用某个外部服务、或者使用特定的提示词模板来完成特定任务。传统的做法是在代码中硬编码这些功能,但这样会导致系统变得越来越臃肿,且难以复用。 + +Skills 系统的设计理念就是将这类"可插拔"的能力封装成独立的技能包。每个 Skill 包含完整的实现文件和元数据,Agent 可以根据配置动态加载所需的技能,实现能力的灵活组合。 + +## 架构设计 + +Skills 系统采用「文件系统存内容,数据库存索引」的分离架构: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Skills 存储架构 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ /app/saves/skills/ 数据库索引 │ +│ ├── skill-a/ ┌──────────────┐ │ +│ │ ├── SKILL.md │ skills 表 │ │ +│ │ ├── tools/ │ - slug │ │ +│ │ └── prompts/ │ - name │ │ +│ └── skill-b/ │ - description│ │ +│ ├── SKILL.md │ - dir_path │ │ +│ └── ... │ - deps... │ │ +│ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 存储结构 + +- **文件系统**:`/app/saves/skills` 目录下,每个 Skill 占用一个子目录 +- **数据库索引**:`skills` 表存储元数据(slug、name、description、依赖关系等) +- **关联机制**:通过 `dir_path` 字段关联文件系统目录与数据库记录 + +::: tip 不能直接在文件系统创建 +由于 Skills 的元数据需要写入数据库,因此不能直接在文件系统中创建 Skill。必须通过系统的导入功能或在线创建功能来完成,系统会自动处理数据库记录的创建。 +::: + +## 创建方式 + +系统提供三种方式创建 Skills: + +1. **ZIP 导入(推荐)**:将 Skill 目录打包成 ZIP,通过管理界面上传导入 +2. **在线创建**:通过 Skills 管理页面在线创建目录和文件 +3. **手动导入**:直接操作数据库(不推荐,需要手动同步文件系统和数据库) + +## Skills 来源 + +Skills 本质上是提示词和工具的封装,以下是一些可以参考的 Skills 实现: + +- **Anthropic 官方 Tools**:https://github.com/anthropics/skills 可以参考其 skills 的组织方式和提示词设计 +- **社区 Skills**:各平台分享的 Agent 提示词模板 +- **自定义开发**:根据业务需求自行开发 + +## 快速开始 + +### 创建你的第一个 Skill + +一个标准的 Skill 目录结构如下: + +``` +my-awesome-skill/ +├── SKILL.md # 必选,Skill 的核心定义文件 +├── tools/ # 可选,相关的工具脚本 +│ └── helper.py +└── prompts/ # 可选,提示词模板 + └── system.md +``` + +其中 `SKILL.md` 是每个 Skill 必须包含的核心文件,它采用 Markdown + Frontmatter 格式: + +```markdown +--- +name: my-awesome-skill +description: 这是一个用于处理特定任务的技能 +--- + +# Skill 使用说明 + +这里是技能的详细使用文档,Agent 会读取这部分内容来了解如何使用这个技能。 + +## 功能列表 + +1. 功能一:xxx +2. 功能二:yyy + +## 使用示例 + +当用户 xxx 时,可以调用此技能... +``` + +**Frontmatter 字段说明:** + +| 字段 | 必填 | 说明 | +|------|------|------| +| `name` | 是 | Skill 名称,必须是小写字母、数字、短横线的组合(如 `my-skill`) | +| `description` | 是 | Skill 的功能描述,会在 Agent 配置时展示 | + +### 导入 Skill + +有两种方式可以导入 Skill: + +**方式一:通过 ZIP 包导入(推荐)** + +1. 将 Skill 目录打包成 ZIP 文件(注意:ZIP 的根目录就是 Skill 目录) +2. 在系统设置的「Skills 管理」页面,点击「导入 Skill」 +3. 上传 ZIP 文件即可 + +系统会自动: +- 校验 ZIP 内容和路径安全性 +- 检查 slug 冲突(如有冲突会自动追加 `-v2` 等后缀) +- 解析 SKILL.md 的 frontmatter 并存储到数据库 + +**方式二:在线创建** + +在 Skills 管理页面,你可以: +- 新建目录或文件 +- 在线编辑文本文件(支持 .md、.py、.js、.json 等格式) +- 直接在网页上编写 SKILL.md 内容 + +## 依赖系统 + +Skills 之间可以建立依赖关系,形成一个松耦合的技能网络。 + +### 依赖类型 + +每个 Skill 可以声明三类依赖: + +| 依赖类型 | 说明 | 加载时机 | +|----------|------|----------| +| `tool_dependencies` | 需要的内置工具 | 激活后按需加载 | +| `mcp_dependencies` | 需要的 MCP 服务 | 激活后按需加载 | +| `skill_dependencies` | 依赖的其他 Skill | 会话启动即生效 | + +### 渐进式加载机制 + +系统采用三级渐进式加载策略,确保资源的高效利用: + +**阶段一:会话启动** + +当 Agent 会话启动时,系统会: +1. 读取 Agent 配置中的 `context.skills` 列表 +2. 递归展开 `skill_dependencies`,构建完整的可见技能集(`visible_skills`) +3. 将可见技能列表注入到系统提示词中 + +这意味着:只要配置了某个 Skill,它的依赖 Skill 就会立即对 Agent 可见。 + +**阶段二:技能激活** + +当 Agent 通过 `read_file` 工具读取 `/skills//SKILL.md` 时,视为"激活"该技能。系统会: +1. 验证该技能在可见列表中 +2. 将其添加到 `activated_skills` 列表 +3. 后续的模型调用会使用激活列表来加载依赖 + +**阶段三:按需加载** + +每次模型调用时,系统会: +1. 检查 `activated_skills` 中的技能 +2. 收集这些技能的 `tool_dependencies` 和 `mcp_dependencies` +3. 动态将需要的工具和 MCP 服务添加到可用工具集中 + +这种设计的好处是:不会在会话开始时加载所有工具,而是根据 Agent 实际使用情况按需加载,既节省资源又保证响应速度。 + +### 依赖声明示例 + +假设我们有三个 Skills: + +- **base-skill**:基础技能,无依赖 +- **advanced-skill**:依赖 `base-skill` +- **pro-skill**:依赖 `advanced-skill` + +当在 Agent 配置中只选择 `pro-skill` 时: +1. 启动阶段:`visible_skills` = [`pro-skill`, `advanced-skill`, `base-skill`](自动展开依赖链) +2. Agent 首次调用任何 skill 时:所有三个 Skill 都可见 +3. 当 Agent 读取 `pro-skill/SKILL.md` 时:触发激活,工具和 MCP 依赖被加载 + +## 权限管理 + +Skills 管理采用基于角色的权限控制: + +| 角色 | 权限 | +|------|------| +| 超级管理员 | 完全控制:导入、导出、编辑、删除、配置依赖 | +| 管理员 | 只读:查看 Skills 列表(用于 Agent 配置) | +| 普通用户 | 无访问权限 | + +管理员可以在创建或编辑 Agent 时,从 Skills 列表中选择需要的能力。 + +## 运行时行为 + +### Agent 如何使用 Skills + +1. **提示词注入**:系统会在 Agent 的系统提示词开头自动插入可用 Skills 的描述 +2. **文件访问**:Skills 目录以只读方式挂载到 `/skills//...` +3. **工具调用**:当 Agent 需要使用某个 Skill 时,会先读取对应的 SKILL.md 了解使用方法 + +### 文件操作限制 + +运行时 `/skills` 路径有以下限制: +- **只读**:Agent 只能读取文件内容 +- **禁止写入**:不能创建、修改或删除文件 +- **路径安全**:所有路径都经过安全校验,防止目录穿越攻击 + +::: tip 虚拟文件系统限制 +当前 Skills 目录挂载为虚拟文件系统,**不支持 shell 命令执行**。Skill 中的脚本仅作为提示词参考,Agent 无法直接执行这些脚本。如果需要执行特定功能,建议通过 MCP 工具或自定义工具实现。 +::: + +### 会话隔离 + +每个 Agent 会话都有独立的 Skills 可见集: +- 不同会话可以配置不同的 Skills +- 同一会话内修改 `context.skills` 会触发快照重建 +- 后台修改 Skills 内容后,已有会话不会自动刷新 + +## 最佳实践 + +### Skill 命名规范 + +- 使用小写字母、数字和短横线 +- 具有描述性,如 `weather-query`、`sql-reporter` +- 避免过长的名称 + +### 依赖管理建议 + +- **保持依赖链简洁**:层级不宜过深,一般 1-2 层为宜 +- **避免循环依赖**:系统会检测并阻止循环依赖 +- **明确依赖必要性**:只在真正需要共享能力时才建立依赖 + +### SKILL.md 编写技巧 + +```markdown +--- +name: example-skill +description: 简短描述技能功能 +--- + +# 技能名称 + +这里是详细的使用说明... + +## 何时使用 + +描述在什么场景下应该使用这个技能... + +## 使用方法 + +1. 第一步... +2. 第二步... + +## 示例 + +``` +具体的使用示例... +``` +``` + +## 常见问题 + +**Q:为什么我配置的 Skill 没有生效?** + +A:请检查以下几点: +1. Skill 的 slug 是否正确配置在 Agent 的 `context.skills` 中 +2. SKILL.md 是否存在且 frontmatter 格式正确 +3. 如果使用了依赖,确保依赖链完整 + +**Q:如何更新已导入的 Skill?** + +A:可以通过以下方式: +1. 导出当前 Skill,修改后重新导入 +2. 在 Skills 管理页面在线编辑文件 +3. 直接修改文件系统中的内容(需要重启服务使缓存失效) + +**Q:Skill 依赖的工具/MCP 不存在怎么办?** + +A:系统会在保存依赖配置时进行校验,如果引用的工具或 MCP 不存在,会报错并阻止保存。 + +--- + +通过 Skills 机制,Yuxi-Know 为 Agent 提供了一个灵活、可扩展的能力扩展框架。你可以将自己积累的业务知识、工具能力封装成 Skills,让不同的 Agent 复用这些能力,极大地提升了系统的可维护性和复用性。 diff --git a/docs/latest/agents/tools-system.md b/docs/latest/agents/tools-system.md new file mode 100644 index 00000000..43f91030 --- /dev/null +++ b/docs/latest/agents/tools-system.md @@ -0,0 +1,66 @@ +# 工具系统 + +Yuxi-Know 提供了统一的工具获取机制,支持多种工具类型的动态组装。 + +## 工具获取机制 + +系统提供统一的工具获取入口 `get_tools_from_context(context)`,它会自动组装三类工具: + +1. **基础工具**:从 `context.tools` 筛选的内置工具 +2. **知识库工具**:根据 `context.knowledges` 自动生成检索工具 +3. **MCP 工具**:根据 `context.mcps` 加载并过滤的 MCP 服务器工具 + +```python +from src.agents.common.tools import get_tools_from_context + +async def get_graph(self, **kwargs): + context = self.get_context() + tools = await get_tools_from_context(context) +``` + +## 工具注册机制 + +Yuxi-Know 的工具系统基于注册机制而非继承体系,这一点与 LangChain 原生的 `@tool` 装饰器有本质区别。 + +LangChain 的 `@tool` 装饰器通常需要继承特定基类或实现特定接口,创建的工具有着强烈的框架耦合。而 Yuxi-Know 的工具注册表是一个独立的全局注册中心,任何符合规范的函数都可以通过 `@tool` 装饰器注册到系统中,无需继承任何基类,也不需要了解框架内部实现。 + +需要特别说明的是,Yuxi-Know 的 `@tool` 装饰器并非全新实现,而是基于 LangChain 原生 `@tool` 的扩展。装饰器的核心逻辑继承自 LangChain,新增了 `category`、`tags`、`display_name` 等元数据字段用于前端展示和分类,原有的 LangChain 特性(如函数参数注解、描述文档等)完全兼容。 + +注册表的核心位于 `src/agents/common/toolkits/registry.py`,它维护着一个全局的工具实例列表。当系统启动时,所有导入 `toolkits` 包的模块都会自动执行其内部的工具注册逻辑,这意味着开发者只需要在自己的模块中添加装饰器,工具就会自动被发现和使用。 + +```python +from src.agents.common.toolkits.registry import tool + +@tool(category="buildin", tags=["计算"], display_name="计算器") +def calculator(a: float, b: float, operation: str) -> float: + """计算器:对给定的2个数字进行基本数学运算""" + if operation == "add": + return a + b + # ... +``` + +使用这个装饰器时,需要指定 `category` 和 `tags`,前者用于工具分类,后者用于前端展示。装饰器内部仍然调用 LangChain 的工具封装逻辑,因此 LangChain 工具的所有特性(如多参数支持、参数类型注解等)都保持兼容。 + +获取工具时,通过 `get_all_tool_instances()` 可以拿到所有已注册的工具实例列表,这个函数会被 `get_tools_from_context` 调用,根据上下文配置筛选出需要使用的工具。 + +## 内置工具 + +系统内置了几类常用工具。计算类包括 calculator,可进行加减乘除运算。搜索类包括 tavily_search,需要在环境变量中配置 `TAVILY_API_KEY` 才能启用。知识图谱类包括 query_knowledge_graph,用于查询通过三元组导入的全局知识图谱。交互类包括 ask_user_question,用于在智能体执行过程中向用户发起交互式提问。数据库类包括 mysql_list_tables、mysql_describe_table 和 mysql_query,用于连接和查询 MySQL 数据库。 + +这些工具都通过上述注册机制自动加载,开发者无需手动引入。 + +## 知识库工具 + +与内置工具不同,知识库工具是动态生成的。当在智能体配置中指定 `context.knowledges` 时,系统会根据指定的 knowledge 名称动态创建对应的检索工具。这种设计使得知识库工具不需要预先注册,而是在运行时按需生成。 + +```python +from src.agents.common.toolkits.kbs import get_common_kb_tools + +kb_tools = get_common_kb_tools(knowledge_names=["kb1", "kb2"]) +``` + +## Skills 集成 + +Skills 与工具是两种不同的扩展机制。工具是具体的功能实现,而 Skills 是包含提示词、工具依赖和元数据的完整技能包。通过 `context.skills` 配置 Skills 时,对应的技能文件会被挂载到 `/skills//...`,智能体可以通过读取 SKILL.md 来了解如何使用这些技能。 + +关于 Skills 的详细机制,请参阅 [Skills 管理](./skills-management.md)。 diff --git a/docs/latest/changelog/contributing.md b/docs/latest/changelog/contributing.md index 9f77ead9..59073f1b 100644 --- a/docs/latest/changelog/contributing.md +++ b/docs/latest/changelog/contributing.md @@ -1,52 +1,51 @@ # 参与贡献 -感谢所有贡献者的支持! +感谢你对 Yuxi-Know 项目的兴趣!我们欢迎任何形式的贡献,包括但不限于代码提交、功能建议、问题反馈和文档改进。 贡献者名单 -## 如何贡献 +## 贡献流程 ### 1. Fork 项目 -在 GitHub 上 Fork 本项目到你的账户。 +在 GitHub 上点击 Fork 按钮,将项目复制到你的账户。 -### 2. 创建分支 +### 2. 创建功能分支 ```bash git checkout -b feature/amazing-feature ``` -### 3. 提交更改 +### 3. 开发并提交 ```bash -git commit -m 'feat: Add some amazing feature' +git commit -m 'feat: 添加新功能' ``` -### 4. 推送分支 +### 4. 推送代码 ```bash git push origin feature/amazing-feature ``` -### 5. 创建 PR +### 5. 创建 Pull Request -在 GitHub 上创建 Pull Request,详细描述你的更改内容。 +在 GitHub 上创建 PR,详细描述你的更改内容和动机。 -## 开发指南 +## 代码规范 -### 代码规范 +项目对代码质量有一定要求,提交前请确保: -- 遵循项目代码规范 - Python 代码使用 `make format` 格式化 - 使用 `make lint` 检查代码质量 - 添加必要的测试用例 - 更新相关文档 -### 提交规范 +## 提交信息规范 -使用清晰的提交信息: +使用清晰规范的提交信息: ``` feat: 添加新功能 @@ -58,29 +57,28 @@ test: 添加测试 chore: 构建过程或辅助工具的变动 ``` +## Bug 修复发布流程 -## 🐞 Bug 修复发布流程 +当发布后发现 bug 需要修复时: -如果在发布 `v0.3.0` 后发现 bug: - -### ✅ 情况 1:main 上没有未完成的新功能 +### 情况 1:main 上没有未完成的新功能 直接在 main 修复并发布: ```bash -git commit -m "fix: resolve config parser crash" +git commit -m "fix: 解决配置解析器崩溃问题" git tag -a v0.3.1 -m "Hotfix v0.3.1" git push origin main --tags ``` -### ⚙️ 情况 2:main 上已有新功能未完成 +### 情况 2:main 上已有新功能未完成 从上一个 tag 建立 hotfix 分支: ```bash git checkout -b hotfix/0.3.1 v0.3.0 # 修复问题 -git commit -m "fix: resolve config parser crash" +git commit -m "fix: 解决配置解析器崩溃问题" git push origin hotfix/0.3.1 # 测试后合并回 main 并打 tag @@ -94,32 +92,29 @@ git branch -d hotfix/0.3.1 git push origin --delete hotfix/0.3.1 ``` +## 测试指南 -### 测试要求 - -::: tip 测试 -- `make lint` / `make format` 保持代码整洁 -- `cp test/.env.test.example test/.env.test` 配置测试凭据 -- `make router-tests` 运行集成路由测试,支持 `PYTEST_ARGS="-k chat_router"` -- `uv run --group test pytest test/api` 可直接运行 pytest(容器内) -::: - -
-常用命令 +### 运行测试 ```bash # 全量路由测试 make router-tests -# 仅运行知识库相关用例 +# 运行特定测试 make router-tests PYTEST_ARGS="-k knowledge_router" -# 不经过 Makefile,直接调用 pytest +# 直接运行 pytest uv run --group test pytest test/api -vv ``` -
+### 测试配置 -## 许可证 +首次运行测试前,需要配置测试凭据: -本项目基于 MIT License 开源,贡献的代码将遵循相同的许可证。 +```bash +cp test/.env.test.example test/.env.test +``` + +--- + +感谢每一位贡献者的付出! diff --git a/docs/latest/changelog/faq.md b/docs/latest/changelog/faq.md index 845556e2..dcacfd13 100644 --- a/docs/latest/changelog/faq.md +++ b/docs/latest/changelog/faq.md @@ -1,70 +1,98 @@ # 常见问题 -以下为最常见的安装与使用问题,更多细节请参阅相应章节链接。 +以下是 Yuxi-Know 在安装和使用过程中最常见的问题及其解决方案。 -## Docker与启动相关问题 +## Docker 与启动问题 -### 镜像拉取/构建失败? -镜像拉取:可使用以下脚本辅助拉取 -- **Linux/macOS**: `docker/pull_image.sh` -- **Windows PowerShell**: `docker/pull_image.ps1` -构建失败:若配置了代理仍失败,可尝试以下步骤: -1. 注释 `api.Dockerfile` 中的代理环境变量设置: - ```dockerfile - # 注释掉以下代理配置 - # ENV HTTP_PROXY=$HTTP_PROXY \ - # HTTPS_PROXY=$HTTPS_PROXY \ - # http_proxy=$HTTP_PROXY \ - # https_proxy=$HTTPS_PROXY - ``` -2. 注释 `docker-compose.yml` 中的代理构建参数: - ```yaml - services: - api: - build: - context: . - dockerfile: docker/api.Dockerfile - # 注释掉代理构建参数 - # args: - # HTTP_PROXY: ${HTTP_PROXY:-} - # HTTPS_PROXY: ${HTTPS_PROXY:-} - ``` -3. 在 `api.Dockerfile` 中添加国内镜像源加速依赖安装: - ```dockerfile - RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --no-dev --index-url https://pypi.tuna.tsinghua.edu.cn/simple +### 镜像拉取或构建失败 + +**镜像拉取问题**: + +```bash +# Linux/macOS +bash docker/pull_image.sh + +# Windows PowerShell +powershell -ExecutionPolicy Bypass -File docker/pull_image.ps1 +``` + +**构建失败问题**: + +如果配置了代理仍然失败,尝试以下步骤: + +1. 注释 `api.Dockerfile` 中的代理配置 +2. 注释 `docker-compose.yml` 中的代理构建参数 +3. 添加国内镜像源加速: + +```dockerfile +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --no-dev --index-url https://pypi.tuna.tsinghua.edu.cn/simple +``` + +### 服务启动失败 + +1. 检查端口占用:`lsof -i :5050` 或 `netstat -tuln | grep 5050` +2. 确认 Docker 服务状态 +3. 查看日志定位问题: + ```bash + docker logs --tail=100 api-dev + docker logs --tail=100 web-dev ``` +### 数据库服务问题 -### 服务启动失败? -- 检查端口占用情况:使用 `lsof -i :5050` 或 `netstat -tuln | grep 5050` 查看端口使用 -- 确认 Docker 服务状态:`systemctl status docker`(Linux)或 `Docker Desktop` 应用状态(Windows/macOS) -- 参考日志定位问题:`docker logs --tail=100 api-dev`、`docker logs --tail=100 web-dev` +**Milvus / Neo4j 启动失败**: -### 服务端口与访问地址? -- Web: `http://localhost:5173`;API 文档: `http://localhost:5050/docs` +```bash +# 重启服务 +docker compose up milvus -d && docker restart api-dev +``` -### Milvus/Neo4j 启动或连接失败? -- 重启:`docker compose up milvus -d && docker restart api-dev` -- Neo4j 默认:用户名 `neo4j`、密码 `0123456789`、管理界面 `http://localhost:7474` -- Milvus 检查:`docker logs milvus -f` 查看启动状态 +**Neo4j 连接信息**: +- 用户名:neo4j +- 密码:0123456789 +- 管理界面:http://localhost:7474 -### 首次运行如何创建管理员? -- Web 首次启动会引导初始化;也可调用 API: - - `GET /api/auth/check-first-run` → `first_run=true` 时 - - `POST /api/auth/initialize` 提交 `user_id` 与 `password` -- 无默认账号,初始化后使用创建的超级管理员登录 +### 账号相关问题 -### 如何查看日志和状态? -- `docker ps` 查看整体服务状态 -- `docker logs api-dev -f`、`docker logs web-dev -f` 查看实时服务日志 -- `docker compose logs --tail=100` 查看所有服务日志 +**首次运行创建管理员**: -## 其他常见问题 +Web 首次启动会引导初始化。也可以通过 API 创建: -### OCR 模型或服务不可用? - - RapidOCR 本地模型:确保 `MODEL_DIR/SWHL/RapidOCR` 下存在 `PP-OCRv4` 模型 - - MinerU/PP-StructureV3:检查健康检查接口与 GPU/CUDA 版本 +```bash +# 检查是否首次运行 +GET /api/auth/check-first-run -### 登录失败被锁定? - - 多次失败会临时锁定账户,请根据提示等待后重试 +# 初始化管理员账号 +POST /api/auth/initialize +# Body: {"user_id": "your_username", "password": "your_password"} +``` + +### 日志查看 + +```bash +# 查看所有容器状态 +docker ps + +# 查看实时日志 +docker logs api-dev -f +docker logs web-dev -f + +# 查看所有服务日志 +docker compose logs --tail=100 +``` + +## 功能使用问题 + +### OCR 服务不可用 + +- **RapidOCR**:确保 `MODEL_DIR/SWHL/RapidOCR` 下存在 `PP-OCRv4` 模型 +- **MinerU / PP-StructureV3**:检查 GPU 和 CUDA 版本是否兼容 + +### 登录失败被锁定 + +多次登录失败会临时锁定账户,请根据页面提示等待后重试。 + +--- + +如果以上问题无法解决你的问题,欢迎在 GitHub Issues 中提问。 diff --git a/docs/latest/intro/evaluation.md b/docs/latest/intro/evaluation.md index fa7e3850..786dcc49 100644 --- a/docs/latest/intro/evaluation.md +++ b/docs/latest/intro/evaluation.md @@ -1,57 +1,80 @@ -# 知识库评估使用与开发指南 +# 知识库评估指南 -知识库评估功能用于测试 RAG 系统的检索和生成质量。通过预设的测试问题和标准答案(或自动生成评估),量化评估系统在不同场景下的表现。 +知识库评估是 RAG 系统开发中的重要环节。通过量化评估,我们可以了解检索和生成的质量,发现问题并持续优化。 -**适用场景**:验证知识库上线前的效果、对比不同配置下的检索效果、定期监控知识库质量变化、调优检索参数。 +## 为什么需要评估 -**注意**:当前版本支持 Milvus 类型的知识库。 +在构建知识库系统时,你可能会遇到这些问题: -## 如何创建评估基准 +- 检索结果不准确,用户找不到想要的内容 +- 生成答案与文档不符,存在幻觉 +- 调整了分块策略或模型,效果是变好还是变差了? -### 1. 上传评估文件 +评估功能就是为了回答这些问题。它通过预设的测试问题和标准答案,量化系统的表现,帮助你做出数据驱动的优化决策。 -准备 JSONL 格式的文件,每行一个测试样本: +## 评估指标解读 + +系统提供以下核心指标: + +| 指标 | 含义 | 参考值 | +|------|------|--------| +| Recall@1 | 第一个检索结果包含正确文档的比例 | > 0.6 为佳 | +| Recall@5 | 前5个检索结果包含正确文档的比例 | > 0.8 为佳 | +| F1@K | 精确率和召回率的调和平均 | 用于横向对比 | +| 答案准确性 | 生成答案与标准答案的一致性 | 越高越好 | + +## 创建评估基准 + +### 手动准备数据 + +准备 JSONL 格式的评估文件,每行一个样本: ```json -{"query": "什么是人工智能?", "gold_chunk_ids": ["chunk_001", "chunk_002"], "gold_answer": "人工智能是计算机科学的一个分支"} -{"query": "机器学习的主要类型有哪些?", "gold_chunk_ids": ["chunk_005"], "gold_answer": "主要包括监督学习、无监督学习和强化学习"} -{"query": "深度学习的应用领域", "gold_chunk_ids": ["chunk_010", "chunk_011"]} +{"query": "什么是人工智能?", "gold_chunk_ids": ["chunk_001"], "gold_answer": "人工智能是..."} +{"query": "机器学习有哪些类型?", "gold_chunk_ids": ["chunk_005"], "gold_answer": "主要包括监督学习..."} ``` -**字段说明**: -- `query`(必需):测试问题,用于触发 RAG 系统的检索 -- `gold_chunk_ids`(可选):相关文档块的 ID 列表,用于验证检索效果 -- `gold_answer`(可选):标准答案,用于验证生成效果 +字段说明: +- `query`:测试问题,必需 +- `gold_chunk_ids`:期望被检索到的文档块 ID,可选 +- `gold_answer`:标准答案,用于评估生成质量,可选 -::: tip 数据集构建 -可以使用 [EasyDataset](https://github.com/ConardLi/easy-dataset) 从文档批量生成问答对、可视化编辑、导出多种格式和数据质量检查。挺好用的,推荐。注意导出的时候的字段需要修改为 `query`、`gold_answer`。 +::: tip 推荐工具 +可以使用 [EasyDataset](https://github.com/ConardLi/easy-dataset) 从文档批量生成问答对。注意导出时将字段名改为 `query` 和 `gold_answer`。 ::: +### 自动生成 -### 2. 自动生成评估基准 +系统也支持自动生成评估数据:随机采样知识库中的文档块,用嵌入模型查找相似内容,最后用大模型生成问答对。 -Yuxi 也实现了一个简易的、可以基于现有知识库自动生成测试数据。流程是:随机采样一个 chunk → 用嵌入模型找相似 chunk → 用 LLM 生成问题和答案。 - -**推荐参数设置**: +推荐参数: - 问题数量:10-50 个 -- 相似文档数:每个问题 2-5 个 +- 相似文档数:2-5 个 -## 运行评估任务 +## 运行评估 -1. 选择评估基准后在知识库页面点击"评估"标签 +在知识库详情页点击「评估」标签,选择评估基准后配置: -2. 配置参数: - - **答案生成模型(可选)**:如果选择了,则会基于检索的 chunk 生成答案,然后用评判模型评估答案的准确性 - - **评判模型(可选)**:如果选择了,则会用评判模型评估答案的准确性,判断是否与标准答案一致,因此选择评判模型时,必须选择答案生成模型。 +1. **答案生成模型**(可选):基于检索到的文档块生成答案 +2. **评判模型**(可选):评估生成答案与标准答案的一致性 -3. 点击"开始评估": -系统会逐个处理测试问题,执行检索和生成,计算各项指标。评估在后台运行,可以继续其他操作。 +点击「开始评估」,系统在后台执行,完成后会显示各项指标结果。 -**主要指标**: +## 评估结果分析 -| 指标 | 含义 | 如何看待 | -|------|------|----------| -| Recall@1 | 第一个结果包含正确文档的比例 | 最重要的指标,反映用户第一眼看到的准确率 | -| Recall@5 | 前5个结果包含正确文档的比例 | 综合检索效果,应该大于 0.8 | -| F1@K | 精确率和召回率的调和平均 | 平衡指标,用于对比不同配置 | -| 答案准确性 | 生成答案是否与标准答案一致 | 检查 LLM 理解和表达能力 | +拿到评估结果后,可以从以下几个角度分析: + +- **Recall@1 低**:说明最相关的内容没有被首先检索到,可能需要调整嵌入模型或分块策略 +- **Recall@5 低**:说明相关文档没有被检索到,可能需要增加检索数量或优化查询 +- **答案准确性低**:说明生成质量有问题,可能需要调整提示词或更换模型 + +## 使用场景 + +- **上线前验证**:知识库建设完成后,评估效果是否满足要求 +- **配置对比**:调整分块策略、嵌入模型后,对比评估结果 +- **定期监控**:定期评估,及时发现质量下降 +- **参数调优**:通过多次评估找到最优参数组合 + +--- + +评估是一个持续的过程。建议在初始建设时就建立评估基准,后续每次重大变更都进行评估,形成数据驱动的优化闭环。 diff --git a/docs/latest/intro/knowledge-base.md b/docs/latest/intro/knowledge-base.md index eb138bc6..ceafe5b8 100644 --- a/docs/latest/intro/knowledge-base.md +++ b/docs/latest/intro/knowledge-base.md @@ -1,145 +1,164 @@ # 知识库与知识图谱 -项目中的知识库与知识图谱,即是知识管理组织的方式,同时会被封装为工具供 AgenticRAG 系统调用。 +Yuxi-Know 提供了强大的知识管理能力,将知识以向量和图谱两种形式存储,既支持传统的语义检索,又能构建结构化的知识关系网络。 -## 知识库介绍 +## 为什么需要知识库 -系统支持多种知识库存储形式,满足不同场景需求: +在大模型应用场景中,仅依靠模型的内部知识往往不够准确和全面。通过构建知识库,我们可以: + +- **注入私有知识**:让模型能够回答基于私有文档的问题 +- **降低幻觉**:回答内容可追溯到原始文档 +- **知识复用**:一次上传,多轮对话中重复使用 + +## 知识库类型 + +系统支持两种知识库存储形式,各有不同的适用场景: | 存储类型 | 特点 | 适用场景 | |----------|------|----------| -| **Milvus** | 高性能向量数据库 | 大规模生产环境、高性能查询 | -| **LightRAG** | 图增强检索 | 复杂知识关系,构建成本较高 | +| **Milvus** | 高性能向量数据库 | 大规模生产环境,需要快速检索 | +| **LightRAG** | 图增强检索 | 复杂知识关系,需要图结构理解 | -访问 Web 界面:`http://localhost:5173`,进入"知识库管理"页面,点击"新建知识库",填写知识库信息。 +选择建议:如果是简单的文档问答,Milvus 就足够了;如果需要理解实体之间的关系,构建知识图谱,LightRAG 是更好的选择。 -这里需要**注意**的是,这里的知识库的标题和描述都会作为智能体选择工具的依据,因此尽量详尽的描述该知识库。 +## 创建知识库 -### 文件上传流程 +访问 Web 界面的「知识库管理」页面,点击「新建知识库」: -文件的处理总共分为三个过程,分别是 上传、解析、入库。上传就是将文件从本地上传到服务器(运行本项目的机器)中,此时文件是以原始文件存储的,比如 PDF 还是 PDF。 -然后会进行第二步解析,即将文件解析成 markdown 格式,其中文件中的图片会被提取出来上传到 minio 数据库中,并在 markdown 文件中的对应位置,添加 url `![图片](minio url)` 这样; -第三步就是入库,这里的入库在 CommonRAG 知识库中,指代的是对 markdown 内容 chunk 后将向量保存到 milvus 中,对于 LightRAG 知识库,则指代的是,提取图谱并保存到知识库中。 +1. 填写知识库名称和描述 +2. 选择存储类型(Milvus 或 LightRAG) +3. 配置访问权限 +4. 保存 -在前端界面中,默认完成前两步,即上传后会自动解析,如果想要实现解析后还继续入库的话,需要在上传的时候勾选自动入库。否则需要在上传后手动点击入库。 +::: tip 提示 +知识库的名称和描述会被智能体用来判断何时应该使用这个知识库进行检索,所以请尽量详细地描述。 +::: +## 文件处理流程 + +文件从上传到可检索,经历三个阶段: + +### 1. 上传阶段 + +将本地文件上传到服务器。文件保持原始格式存储(PDF 还是 PDF,Word 还是 Word)。 + +### 2. 解析阶段 + +系统将文件转换为 Markdown 格式: +- 提取文本内容 +- 图片上传到 MinIO,并在 Markdown 中用 URL 引用 +- 表格、公式等保持结构化 + +### 3. 入库阶段 + +- **Milvus 知识库**:对 Markdown 内容进行分块,向量存储到 Milvus +- **LightRAG 知识库**:提取实体和关系,构建知识图谱到 Neo4j + +在前端界面中,默认会自动完成前两个阶段。如果需要自动入库,勾选「上传后自动入库」选项;否则需要手动点击入库按钮。 ## 知识库权限控制 每个知识库可以配置独立的访问权限: -- **共享模式**: 设置知识库是否全局共享 -- **部门访问**: 配置允许访问该知识库的部门范围 +- **全局共享**:所有用户可访问 +- **部门授权**:仅指定部门可访问 +- **私有**:仅创建者和管理员可访问 权限规则: -- **超级管理员**: 可访问所有知识库 -- **管理员**: 可访问共享以及本部门所有知识库 -- **普通用户**: 仅能访问已授权的知识库(通过部门或全局共享) - -创建/编辑知识库时,可在"分享配置"中设置权限。 - -## 文档管理 - -本系统的“上传 → 解析入库 → 检索/可视化”流程既可通过 Web 界面完成,也可使用 API/脚本批量处理。详见[文档解析](../advanced/document-processing.md) - -接口查询:`GET /api/knowledge/files/supported-types` - -**上传与入库** - -1) 上传文件(返回服务端保存路径) -- `POST /api/knowledge/files/upload?db_id=<可选>` -- 成功返回:`file_path`(后续入库使用)、`content_hash`(内容去重) - -2) 解析并入库(异步任务) -- `POST /api/knowledge/databases/{db_id}/documents` -- 返回:`status=queued` 与 `task_id`,可在任务中心查看进度 - -去重策略:系统按“内容哈希”判断是否已存在相同文件,避免重复入库。 - -## 其他 - -### LightRAG 知识库说明 - -在本项目中,系统支持基于 [LightRAG](https://github.com/HKUDS/LightRAG) 的知识图谱自动构建,能够从文档中自动提取实体和关系,构建结构化知识图谱。 - -**LightRAG 图谱 vs 全局知识图谱的区别:** - -- **LightRAG 图谱**(知识库专属):针对单个知识库由 LightRAG 自动抽取实体/关系,用于该库内的图增强检索与可视化。通过特殊的 label(知识库ID)与全局图谱区分,不会混入全局数据。 - -- **全局知识图谱**(系统级):通过三元组文件上传的图谱数据,提供系统级的知识图谱查询和可视化能力,会作为工具供 LLM 使用。 - -两者共享同一个 Neo4j 实例,但完全隔离,互不影响。 - -LightRAG 知识库可在知识库详情、知识图谱中可视化。由于免费版的 neo4j 只能创建一个图数据库,因此实际上 LightRAG 的节点和边依然是和知识图谱本身构建在了同一个 Neo4j 数据库中,但是使用了特殊的 label `{知识库ID}` 做区分。 - -**常见问题** - -1. 只有节点没有边/出现了 TPM 的报错:大概率是由于供应商限制了模型的调用量,解决办法是更换 TPM 更大的模型,或其他供应商。 -2. 当本地计算资源有限时,可以配置 `EMBEDDING_TIMEOUT=60`, `LLM_TIMEOUT=180` 增加超时时间 - -同时项目支持原 LightRAG 的所有环境变量,只需要在项目的 `.env` 文件中配置即可。 +- 超级管理员可访问所有知识库 +- 管理员可访问共享知识库和本部门的知识库 +- 普通用户只能访问已授权的知识库 ## 知识图谱 -本项目存在两类“图谱相关”能力: +系统支持两种图谱相关能力,理解它们的区别很重要: -- 上传的知识图谱(Neo4j):提供三元组检索和系统级可视化。会作为工具供 LLM 使用。 -- LightRAG 知识库内图谱:针对某个知识库由 LightRAG 自动抽取实体/关系,用于该库内的图增强检索与可视化;与上传的图谱共享同一 Neo4j 实例,但通过特殊 label 区分,不作为全局图谱使用。 +### LightRAG 图谱 +针对单个知识库由 LightRAG 自动抽取实体和关系。特点: +- 自动从文档中提取 +- 附属于特定知识库 +- 用于该知识库内的图增强检索 +- 通过知识库 ID 作为 Label 区分,与全局图谱隔离 -### 1. 以三元组形式导入 +### 全局知识图谱 -系统支持通过网页导入 `jsonl` 格式的知识图谱数据,支持**简单三元组**和**带属性三元组**两种格式。 +通过三元组文件上传的图谱数据。特点: +- 手动导入 +- 系统级知识库 +- 提供图查询和可视化能力 +- 作为工具供智能体调用 -**简单格式(兼容旧版)**: +两者共享同一个 Neo4j 实例,但数据完全隔离,互不影响。 -```jsonl +### 导入三元组数据 + +系统支持通过网页导入 `jsonl` 格式的图谱数据: + +**简单格式**: +```json {"h": "北京", "t": "中国", "r": "首都"} {"h": "上海", "t": "中国", "r": "直辖市"} ``` **扩展格式(支持属性)**: - -支持 `h`(头节点)、`t`(尾节点)和 `r`(关系)为对象结构,其中: -- 节点对象必须包含 `name` 字段。 -- 关系对象必须包含 `type` 字段。 -- 其他字段将作为**属性**存储在 Neo4j 中。 - -```jsonl -{"h": {"name": "孙悟空", "title": "齐天大圣", "weapon": "如意金箍棒"}, "t": {"name": "唐僧", "species": "人"}, "r": {"type": "徒弟", "order": 1}} -{"h": "猪八戒", "t": {"name": "唐僧"}, "r": {"type": "徒弟", "order": 2}} +```json +{"h": {"name": "孙悟空", "title": "齐天大圣"}, "t": {"name": "唐僧"}, "r": {"type": "徒弟"}} ``` -**格式说明**: -- 每行一个数据项。 -- 系统自动验证数据格式,并自动导入到 Neo4j 数据库。 -- 自动添加 `Upload`、`Entity` 标签(节点)和 `RELATION` 类型(关系)。 -- 自动处理重复实体和关系,并合并属性。 +导入后,可以在图谱可视化页面查看和查询。 -Neo4j 访问信息可以参考 `docker-compose.yml` 中配置对应的环境变量来覆盖。 +### Neo4j 配置 -- **默认账户**: `neo4j` -- **默认密码**: `0123456789` -- **管理界面**: `http://localhost:7474` -- **连接地址**: bolt://localhost:7687 - -::: tip 测试数据 -可以使用以下文件进行测试导入: -- 简单格式:`test/data/A_Dream_of_Red_Mansions_tiny.jsonl` -- 扩展属性格式:`test/data/complex_graph_test.jsonl` -::: - -### 2. 接入已有 Neo4j 实例 - -如需接入已有的 Neo4j 实例,可修改 `.env` 中的配置: - -<<< @/../.env.template#neo4j{bash} - -同时记得注释掉下面的 neo4j 服务: - -<<< @/../docker-compose.yml#neo4j +Neo4j 连接信息可以在 `.env` 中配置: +- 默认账户:`neo4j` +- 默认密码:`0123456789` +- 管理界面:http://localhost:7474 +- 连接地址:bolt://localhost:7687 ::: warning 注意事项 确保每个节点都有 `Entity` 标签和 `name` 属性,每个关系都有 `RELATION` 类型和 `type` 属性,否则会影响图的检索与构建功能。 ::: + +## 常见问题 + +**Q:只有节点没有边怎么办?** + +A:这通常是因为模型调用量受限。请尝试: +- 更换 TPM(每分钟令牌数)更大的模型 +- 更换模型服务商 + +**Q:构建图谱时出现超时错误?** + +A:可以配置增加超时时间: +```env +EMBEDDING_TIMEOUT=60 +LLM_TIMEOUT=180 +``` + +**Q:LightRAG 和全局图谱有什么区别?** + +A:简单理解: +- LightRAG 图谱 = 自动从知识库文档中提取,附属于知识库 +- 全局图谱 = 手动导入,系统级图谱查询 + +## API 使用 + +如果需要通过程序批量处理文件,可以使用以下接口: + +```bash +# 1. 上传文件 +POST /api/knowledge/files/upload?db_id=<知识库ID> +# 返回 file_path 和 content_hash + +# 2. 解析并入库 +POST /api/knowledge/databases/{db_id}/documents +# 返回 status=queued 和 task_id +``` + +系统会自动去重:基于内容哈希判断是否已存在相同文件。 + +--- + +知识库是 Yuxi-Know 的核心能力之一,通过本文档的介绍,你应该能够掌握创建和使用知识库的基本方法。对于更高级的用法,如评估基准构建、图增强检索优化等,可以进一步探索系统的其他功能。 diff --git a/docs/latest/intro/model-config.md b/docs/latest/intro/model-config.md index 5bcfbbb6..74c38b00 100644 --- a/docs/latest/intro/model-config.md +++ b/docs/latest/intro/model-config.md @@ -152,6 +152,35 @@ models = [ 3. **权限错误**: 确保用户具有管理员权限 4. **配置未生效**: 检查环境变量配置和服务重启状态 +## 多模态模型 + +系统支持图片作为输入,与文本结合形成多模态查询。 + +### 支持的图片格式 + +- JPEG、PNG、WebP、GIF、BMP +- 最大 10MB +- 超过 5MB 会自动压缩 + +### 使用方式 + +在对话接口中传入图片数据: + +```json +{ + "query": "这张图片里有什么?", + "image_content": "", + "config": {}, + "meta": {} +} +``` + +系统会自动将图片转换为符合模型要求的格式,支持多模态的模型会同时处理图片和文本信息。 + +### 支持多模态的模型 + +大多数主流模型提供商都支持多模态能力,选择模型时需确认模型本身支持图片输入。 + ## 嵌入模型和重排序模型 #### 1. 配置模型信息 diff --git a/docs/latest/intro/project-overview.md b/docs/latest/intro/project-overview.md index b4db6425..b07e61a3 100644 --- a/docs/latest/intro/project-overview.md +++ b/docs/latest/intro/project-overview.md @@ -1,24 +1,77 @@ # 项目简介 -Yuxi-Know(语析)是一个基于知识图谱和向量数据库的智能知识库系统,融合了 RAG(检索增强生成)技术与知识图谱技术,为用户提供智能问答和知识管理服务。 +Yuxi-Know(语析)是一个基于大模型的智能知识库与知识图谱智能体开发平台。它融合了 RAG(检索增强生成)技术与知识图谱技术,为用户提供智能问答和知识管理服务。 -**特点**:技术栈简单,易于上手,使用 MIT 开源协议,非常适合二次开发使用。 +## 设计理念 -### 技术栈选择 +项目的设计目标是为开发者提供一个易于上手、功能强大的 AI 应用开发框架。我们坚持以下原则: -- **后端服务**: [FastAPI](https://github.com/tiangolo/fastapi) + Python 3.12+ -- **前端界面**: [Vue.js 3](https://github.com/vuejs/vue) + [Ant Design Vue](https://github.com/vueComponent/ant-design-vue) -- **数据库存储**: [PostgreSQL](https://github.com/postgres/postgres) + [MinIO](https://github.com/minio/minio) -- **知识存储**: [Milvus](https://github.com/milvus-io/milvus)(向量数据库)+ [Neo4j](https://github.com/neo4j/neo4j)(图数据库) -- **智能体框架**: [LangGraph](https://github.com/langchain-ai/langgraph) -- **文档解析**: [LightRAG](https://github.com/HKUDS/LightRAG) + [MinerU](https://github.com/HKUDS/MinerU) + [PP-Structure-V3](https://github.com/PaddlePaddle/PaddleOCR) -- **容器编排**: [Docker Compose](https://github.com/docker/compose) +- **技术栈简洁**:选择主流且成熟的技术,降低学习和维护成本 +- **MIT 开源协议**:完全开源,允许自由使用和二次开发 +- **容器化部署**:通过 Docker Compose 管理,简化部署流程 -### 核心功能 +## 技术架构 -- **智能问答**: 支持多种大语言模型,提供智能对话和问答服务 -- **知识库管理**: 支持多种存储形式(Milvus、LightRAG) -- **知识图谱**: 自动构建和可视化知识图谱,支持图查询 -- **文档解析**: 支持 PDF、Word、图片等多种格式的智能解析 -- **权限管理**: 基于部门的知识库访问控制 -- **内容安全**: 内置内容审查机制,保障服务合规性 +### 后端服务 + +- **FastAPI**:现代高性能 Python Web 框架 +- **LangGraph**:基于 LangChain 的智能体编排框架 +- **PostgreSQL**:业务数据存储 +- **Milvus**:向量数据库,支持大规模语义检索 +- **Neo4j**:图数据库,存储知识图谱 +- **MinIO**:对象存储,用于文件托管 + +### 前端界面 + +- **Vue.js 3**:渐进式前端框架 +- **Ant Design Vue**:企业级 UI 组件库 + +### 文档处理 + +- **LightRAG**:文档理解与知识图谱构建 +- **MinerU**:文档智能解析 +- **PP-Structure-V3**:PDF 结构化提取 + +## 核心能力 + +### 智能问答 + +系统支持接入多种大语言模型,通过对话方式提供智能问答服务。模型可配置、工具可组合、提示词可定制,满足不同业务场景需求。 + +### 知识库管理 + +支持 Milvus 向量数据库和 LightRAG 知识图谱两种存储形式: +- Milvus 适合大规模文档检索场景 +- LightRAG 适合需要理解实体关系的复杂查询 + +### 知识图谱 + +自动从文档中提取实体和关系,构建结构化知识图谱。支持可视化查看和图查询,帮助理解知识之间的联系。 + +### 文档解析 + +支持 PDF、Word、图片等多种格式的智能解析,自动提取文本、表格、公式等内容。 + +### 权限管理 + +基于部门的知识库访问控制,确保数据安全。 + +### 内容安全 + +内置内容审查机制,保障服务合规性。 + +## 适用场景 + +Yuxi-Know 适用于以下场景: + +- **企业知识库**:构建私有知识问答系统 +- **智能客服**:基于文档的自动问答 +- **知识管理**:文档自动解析、分类、构建图谱 +- **AI 应用开发**:快速构建基于大模型的应用原型 + +## 下一步 + +- 快速开始:阅读 [快速开始指南](./quick-start.md) +- 模型配置:阅读 [模型配置](./model-config.md) +- 知识库使用:阅读 [知识库与知识图谱](./knowledge-base.md) +- 智能体开发:阅读 [智能体开发](../agents/agents-config.md) diff --git a/docs/latest/intro/quick-start.md b/docs/latest/intro/quick-start.md index ad32bff6..1f6ffcba 100644 --- a/docs/latest/intro/quick-start.md +++ b/docs/latest/intro/quick-start.md @@ -1,36 +1,37 @@ # 快速开始指南 +Yuxi-Know(语析)是一个基于知识图谱和向量数据库的智能知识库系统。通过本文档,你可以在几分钟内完成环境搭建并开始使用。 + ::: tip 提示 -除了此文档网站外,用户还可以在 [Zread](https://zread.ai/xerrors/Yuxi-Know) 或 [DeepWiki](https://deepwiki.com/xerrors/Yuxi-Know) 平台查看自动生成的详细项目文档。 +除了此文档网站外,你还可以访问 [Zread](https://zread.ai/xerrors/Yuxi-Know) 或 [DeepWiki](https://deepwiki.com/xerrors/Yuxi-Know) 查看自动生成的详细项目文档。 ::: +## 环境要求 -## 快速开始 +项目采用微服务架构设计,默认服务无需 GPU 支持。如果需要使用 OCR 功能,可以通过环境变量配置外部服务。 +## 快速安装 -### 安装步骤 - -项目采用微服务架构,默认服务无需 GPU 支持。GPU 仅用于可选的 OCR 服务,可通过环境变量配置外部服务。 - -#### 1. 获取项目代码 +### 步骤一:获取项目代码 ```bash -# 克隆稳定版本 +# 克隆稳定版本(推荐新用户使用 v0.5.1) git clone --branch v0.5.1 --depth 1 https://github.com/xerrors/Yuxi-Know.git cd Yuxi-Know ``` -::: warning 版本说明 -- `v0.5.0`: 稳定版本,由于数据库重构使用 postgres,可能会存在数据库迁移问题,建议新用户使用,迁移指南详见 [迁移指南](https://xerrors.github.io/Yuxi-Know/latest/changelog/migrate_to_v0-5)。 -- `v0.5.1`: -- `main`: 最新开发版本(不稳定,新特性可能会导致新 bug) -::: +版本选择建议: -#### 2. 项目启动 +| 版本 | 适用场景 | +|------|----------| +| v0.5.x | 稳定版本,适合生产环境使用 | +| main | 开发版本,包含最新特性(可能不稳定) | -**方法 1**:使用 init 脚本(推荐) +### 步骤二:配置环境变量 -我们提供了自动化的初始化脚本,可以帮您完成环境配置和 Docker 镜像拉取: +**方式一:使用初始化脚本(推荐)** + +我们提供了自动化脚本,帮你完成环境配置和 Docker 镜像拉取: ```bash # Linux/macOS @@ -40,127 +41,108 @@ cd Yuxi-Know .\scripts\init.ps1 ``` -脚本会: -- 检查并创建 `.env` 文件 -- 提示您输入 `SILICONFLOW_API_KEY`(必需) -- 提示您输入 `TAVILY_API_KEY`(可选,用于搜索服务) -- 自动拉取所有必需的 Docker 镜像 +脚本会引导你完成以下配置: +- 创建 `.env` 配置文件 +- 设置 `SILICONFLOW_API_KEY`(必需,用于调用大模型) +- 设置 `TAVILY_API_KEY`(可选,用于搜索服务) +- 自动拉取必需的 Docker 镜像 ::: tip API Key 获取 -- [硅基流动](https://cloud.siliconflow.cn/i/Eo5yTHGJ) 注册即送 14 元额度 -- [Tavily](https://app.tavily.com/) 获取搜索服务 API Key(可选) +- **硅基流动**:访问 [cloud.siliconflow.cn](https://cloud.siliconflow.cn/i/Eo5yTHGJ),注册即送 14 元额度 +- **Tavily**:访问 [app.tavily.com](https://app.tavily.com/) 获取搜索 API Key(可选) ::: -**方法 2**:手动配置环境变量 +**方式二:手动配置** -复制环境变量模板并编辑: +如果偏好手动配置: ```bash +# 复制环境变量模板 cp .env.template .env + +# 编辑 .env 文件,填入你的 API Key ``` -编辑 `.env` 文件,配置必需的 API 密钥,这里强烈建议先使用硅基流动的 API 和模型(DeepSeek)验证平台的功能无误后,再尝试切换到自己的模型: - - -<<< @/../.env.template#model_provider{bash 5} - - -::: tip 免费获取 API Key -[硅基流动](https://cloud.siliconflow.cn/i/Eo5yTHGJ) 注册即送 14 元额度,支持多种开源模型。 -::: - -#### 3. 启动服务 +### 步骤三:启动服务 ```bash # 构建并启动所有服务 -docker compose up --build - -# 后台运行(推荐) docker compose up --build -d ``` -**注意**:启动后,可能还需要一些时间,尤其是后端服务需要一段时间,请耐心等待 2-3 分钟。 +服务首次启动需要等待镜像拉取和编译,请耐心等待 2-3 分钟。 -#### 4. 访问系统 +### 步骤四:访问系统 -服务启动完成后,访问以下地址: +服务启动后,访问以下地址: -- **Web 界面**: `http://localhost:5173` -- **API 文档**: `http://localhost:5050/docs` +| 服务 | 地址 | +|------|------| +| Web 界面 | http://localhost:5173 | +| API 文档 | http://localhost:5050/docs | -#### 5. 停止服务 +首次访问时,系统会要求你设置超级管理员账号和密码,请妥善保存。 -```bash -docker compose down -``` +## 开始使用 -## 对话 - -项目第一次启动后,会要求填写超级管理员账号和密码,请确保填写正确。 - -然后在智能体页面可以进行对话,在右侧可以配置提示词、模型、工具等参数。 - -![agent.png](/images/agent.png) +完成上述配置后,你就可以开始使用了: +1. 登录系统(使用刚才设置的超级管理员账号) +2. 进入「智能体」页面 +3. 选择或创建一个智能体 +4. 在右侧面板配置提示词、选择模型和工具 +5. 开始对话 +![智能体配置界面](/images/agent.png) ## 故障排除 -::: tip 调试面板 -前端有个**调试面板**,在头像选项里,生产环境建议删除此特性。 -::: - -#### 查看服务状态 +### 查看服务状态 ```bash # 查看所有容器状态 docker ps -# 查看后端服务日志 +# 实时查看后端日志 docker logs api-dev -f -# 查看前端服务日志 +# 实时查看前端日志 docker logs web-dev -f ``` -#### 常见问题 +### 常见问题
Docker 镜像拉取失败 -如果拉取镜像失败,可以尝试手动拉取: +如果网络原因导致镜像拉取失败,可以尝试: ```bash -# Linux/macOS +# 手动拉取基础镜像 bash docker/pull_image.sh python:3.12-slim - -# Windows PowerShell -powershell -ExecutionPolicy Bypass -File docker/pull_image.ps1 python:3.12-slim ``` -**离线镜像拉取方案**: +**离线环境部署方案**: ```bash -# 在有网络的环境保存镜像(镜像名称需要确认是否和实际一致,现有版本可能不是最新最全,需要检查) -bash docker/save_docker_images.sh # Linux/macOS -powershell -ExecutionPolicy Bypass -File docker/save_docker_images.ps1 # Windows +# 在有网络的环境导出镜像 +bash docker/save_docker_images.sh -# 传输到目标设备 -scp docker_images_xxx.tar @: +# 传输到目标机器 +scp docker_images_xxx.tar user@host:/path/ -# 在目标设备加载镜像 +# 导入镜像 docker load -i docker_images_xxx.tar ``` -
构建失败 -如果构建失败,通常是网络问题,可以配置代理: +多数构建失败是由于网络问题。尝试配置代理: ```bash -# Linux / macOS +# Linux/macOS export HTTP_PROXY=http://IP:PORT export HTTPS_PROXY=http://IP:PORT @@ -169,21 +151,25 @@ $env:HTTP_PROXY="http://IP:PORT" $env:HTTPS_PROXY="http://IP:PORT" ``` -如果已配置代理但构建失败,尝试移除代理后重试。 - -如果出现,FetchError: request to https://registry.npmjs.org/npm failed, reason: connect ECONNREFUSED 127.0.0.1:7890 - -新建一个终端重新执行,并确保没有代理干扰。 - +如果配置代理后反而失败,尝试移除代理后重试。
-Milvus 启动失败 +Milvus 服务启动失败 ```bash # 重启 Milvus 服务 docker compose up milvus -d docker restart api-dev ``` -
+ +::: tip 调试面板 +前端提供了调试面板(在头像菜单中可找到),可以查看详细的请求和响应信息。生产环境建议关闭此特性。 +::: + +## 下一步 + +- 了解如何配置模型:阅读 [模型配置](./model-config.md) +- 探索知识库功能:阅读 [知识库与知识图谱](./knowledge-base.md) +- 学习智能体开发:阅读 [智能体开发](../agents/agents-config.md) From 4477c8d274085515c9c5ee838b2153190af8c4e5 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Tue, 10 Mar 2026 15:02:40 +0800 Subject: [PATCH 23/24] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20srcDir=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=92=8C=E5=BF=BD=E7=95=A5=E6=AD=BB=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vitepress/config.mts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 54ca1334..164b7ff6 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -8,6 +8,10 @@ export default defineConfig({ title: "Yuxi-Know", description: "语析", base: '/Yuxi-Know/', + srcDir: './', + ignoreDeadLinks: [ + /localhost/ + ], markdown: { config: (md) => { md.use(markdownItTaskCheckbox) From 608a42bcb3f98f96d78731ad9a0306ade9d65fb8 Mon Sep 17 00:00:00 2001 From: zhizhizhii <1158222167@qq.com> Date: Sat, 14 Mar 2026 15:58:35 +0800 Subject: [PATCH 24/24] =?UTF-8?q?fix:=20=E6=B5=8B=E8=AF=95=E6=B2=99?= =?UTF-8?q?=E7=AE=B1=EF=BC=8C=E4=BF=AE=E5=A4=8D=E8=8B=A5=E5=B9=B2=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 9 ++- docker/sandbox_provisioner/app.py | 33 ++++++--- pyproject.toml | 1 + src/knowledge/manager.py | 19 +++-- src/repositories/conversation_repository.py | 14 +++- src/services/chat_stream_service.py | 2 +- uv.lock | 82 +++++++++++++++------ 7 files changed, 115 insertions(+), 45 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index f262905d..09931b2b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ services: build: context: . dockerfile: docker/api.Dockerfile - image: yuxi-api:0.5.dev + image: yuxi-api:0.5.2.dev container_name: api-dev working_dir: /app volumes: @@ -73,7 +73,7 @@ services: build: context: . dockerfile: docker/api.Dockerfile - image: yuxi-api:0.5.dev + image: yuxi-api:0.5.2.dev container_name: worker-dev working_dir: /app volumes: @@ -135,6 +135,7 @@ services: build: context: ./docker/sandbox_provisioner dockerfile: Dockerfile + image: yuxi-sandbox-provisioner:0.5.2.dev container_name: sandbox-provisioner volumes: - ./saves:/app/saves @@ -181,7 +182,7 @@ services: context: . dockerfile: docker/web.Dockerfile target: development - image: yuxi-web:0.5.dev + image: yuxi-web:0.5.2.dev container_name: web-dev volumes: - ./web/src:/app/src @@ -324,6 +325,8 @@ services: redis: image: redis:7-alpine container_name: redis + ports: + - "6379:6379" command: redis-server --appendonly yes healthcheck: test: ["CMD", "redis-cli", "ping"] diff --git a/docker/sandbox_provisioner/app.py b/docker/sandbox_provisioner/app.py index 87a957e5..f8631068 100644 --- a/docker/sandbox_provisioner/app.py +++ b/docker/sandbox_provisioner/app.py @@ -236,16 +236,31 @@ class LocalContainerProvisionerBackend: raise RuntimeError(f"sandbox {sandbox_id} is not ready at {record.sandbox_url}") return record - threads_root = Path(self._threads_host_path).resolve() - thread_user_data = (threads_root / safe_thread_id / "user-data").resolve() - try: - thread_user_data.relative_to(threads_root) - except ValueError as exc: - raise ValueError("thread_id resolved outside threads host root") from exc - thread_user_data.mkdir(parents=True, exist_ok=True) + # 检测是否是 Windows 绝对路径 (如 D:/ 或 D:\) + threads_root_str = self._threads_host_path + is_windows_path = len(threads_root_str) >= 2 and threads_root_str[1] == ':' - skills_path = Path(self._skills_host_path) - skills_path.mkdir(parents=True, exist_ok=True) + if is_windows_path: + # Windows 路径,直接使用,不调用 resolve() + threads_root = Path(threads_root_str) + thread_user_data = threads_root / safe_thread_id / "user-data" + # Windows 路径下无法在 Linux 容器内创建目录,跳过 mkdir + else: + threads_root = Path(threads_root_str).resolve() + thread_user_data = (threads_root / safe_thread_id / "user-data").resolve() + try: + thread_user_data.relative_to(threads_root) + except ValueError as exc: + raise ValueError("thread_id resolved outside threads host root") from exc + thread_user_data.mkdir(parents=True, exist_ok=True) + + skills_path_str = self._skills_host_path + is_skills_windows = len(skills_path_str) >= 2 and skills_path_str[1] == ':' + if is_skills_windows: + skills_path = Path(skills_path_str) + else: + skills_path = Path(skills_path_str) + skills_path.mkdir(parents=True, exist_ok=True) container_name = self._container_name(sandbox_id) run_kwargs = { diff --git a/pyproject.toml b/pyproject.toml index 2cbfac1c..3b38b6f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ "langgraph>=1.0.1", "langgraph-checkpoint-sqlite>=3.0", "langgraph-checkpoint-postgres>=2.0.0", + "psycopg[binary]>=3.2.0", "langgraph-cli[inmem]>=0.4", "langsmith>=0.4", "lightrag-hku>=1.4.6", diff --git a/src/knowledge/manager.py b/src/knowledge/manager.py index 25e4a40a..f266b165 100644 --- a/src/knowledge/manager.py +++ b/src/knowledge/manager.py @@ -268,16 +268,21 @@ class KnowledgeBaseManager: return {"databases": []} return await self.get_databases_by_user(user) - async def get_databases_by_user(self, user: User) -> dict: + async def get_databases_by_user(self, user: User | dict) -> dict: """根据用户权限获取知识库列表""" - # 构建用户信息字典 - user_info = { - "role": user.role, - "department_id": user.department_id, - } + # 构建用户信息字典(支持 User 对象或 dict) + if isinstance(user, dict): + user_info = user + else: + user_info = { + "role": user.role, + "department_id": user.department_id, + } - logger.info(f"Getting databases for user {user.id} with role {user.role} and department {user.department_id}") + user_role = user_info.get("role") + user_dept = user_info.get("department_id") + logger.info(f"Getting databases for user with role {user_role} and department {user_dept}") all_databases = (await self.get_databases()).get("databases", []) diff --git a/src/repositories/conversation_repository.py b/src/repositories/conversation_repository.py index ae8d3f0f..ee49e64c 100644 --- a/src/repositories/conversation_repository.py +++ b/src/repositories/conversation_repository.py @@ -151,6 +151,15 @@ class ConversationRepository: error_message: str | None = None, langgraph_tool_call_id: str | None = None, ) -> ToolCall: + if langgraph_tool_call_id: + existing = await self.get_tool_call_by_langgraph_id(langgraph_tool_call_id) + if existing: + logger.debug( + "Tool call already exists for langgraph_tool_call_id=%s, skip insert", + langgraph_tool_call_id, + ) + return existing + tool_call = ToolCall( message_id=message_id, tool_name=tool_name, @@ -333,7 +342,10 @@ class ConversationRepository: async def get_tool_call_by_langgraph_id(self, langgraph_tool_call_id: str) -> ToolCall | None: result = await self.db.execute( - select(ToolCall).where(ToolCall.langgraph_tool_call_id == langgraph_tool_call_id) + select(ToolCall) + .where(ToolCall.langgraph_tool_call_id == langgraph_tool_call_id) + .order_by(ToolCall.created_at.desc()) + .limit(1) ) return result.scalar_one_or_none() diff --git a/src/services/chat_stream_service.py b/src/services/chat_stream_service.py index fb482161..31b046d5 100644 --- a/src/services/chat_stream_service.py +++ b/src/services/chat_stream_service.py @@ -9,7 +9,7 @@ from typing import Any from langchain.messages import AIMessage, AIMessageChunk, HumanMessage from langgraph.types import Command -from src import config as conf +from src import config as conf, knowledge_base from src.agents import agent_manager from src.plugins.guard import content_guard from src.repositories.agent_config_repository import AgentConfigRepository diff --git a/uv.lock b/uv.lock index 2b8b0fef..ab7edcff 100644 --- a/uv.lock +++ b/uv.lock @@ -953,8 +953,8 @@ dependencies = [ { name = "safetensors", extra = ["torch"] }, { name = "torch", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, - { name = "torchvision", version = "0.23.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, - { name = "torchvision", version = "0.23.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchvision", version = "0.23.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "torchvision", version = "0.23.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "tqdm" }, { name = "transformers" }, ] @@ -3398,6 +3398,40 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768, upload-time = "2026-02-18T16:46:27.365Z" }, ] +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.3.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/15/021be5c0cbc5b7c1ab46e91cc3434eb42569f79a0592e67b8d25e66d844d/psycopg_binary-3.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6698dbab5bcef8fdb570fc9d35fd9ac52041771bfcfe6fd0fc5f5c4e36f1e99d", size = 4591170, upload-time = "2026-02-18T16:48:55.594Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/54/a60211c346c9a2f8c6b272b5f2bbe21f6e11800ce7f61e99ba75cf8b63e1/psycopg_binary-3.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:329ff393441e75f10b673ae99ab45276887993d49e65f141da20d915c05aafd8", size = 4670009, upload-time = "2026-02-18T16:49:03.608Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/53/ac7c18671347c553362aadbf65f92786eef9540676ca24114cc02f5be405/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:eb072949b8ebf4082ae24289a2b0fd724da9adc8f22743409d6fd718ddb379df", size = 5469735, upload-time = "2026-02-18T16:49:10.128Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/c3/4f4e040902b82a344eff1c736cde2f2720f127fe939c7e7565706f96dd44/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:263a24f39f26e19ed7fc982d7859a36f17841b05bebad3eb47bb9cd2dd785351", size = 5152919, upload-time = "2026-02-18T16:49:16.335Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/e7/d929679c6a5c212bcf738806c7c89f5b3d0919f2e1685a0e08d6ff877945/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5152d50798c2fa5bd9b68ec68eb68a1b71b95126c1d70adaa1a08cd5eefdc23d", size = 6738785, upload-time = "2026-02-18T16:49:22.687Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/b0/09703aeb69a9443d232d7b5318d58742e8ca51ff79f90ffe6b88f1db45e7/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d6a1e56dd267848edb824dbeb08cf5bac649e02ee0b03ba883ba3f4f0bd54f2", size = 4979008, upload-time = "2026-02-18T16:49:27.313Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/a6/e662558b793c6e13a7473b970fee327d635270e41eded3090ef14045a6a5/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73eaaf4bb04709f545606c1db2f65f4000e8a04cdbf3e00d165a23004692093e", size = 4508255, upload-time = "2026-02-18T16:49:31.575Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/7f/0f8b2e1d5e0093921b6f324a948a5c740c1447fbb45e97acaf50241d0f39/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:162e5675efb4704192411eaf8e00d07f7960b679cd3306e7efb120bb8d9456cc", size = 4189166, upload-time = "2026-02-18T16:49:35.801Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/ec/ce2e91c33bc8d10b00c87e2f6b0fb570641a6a60042d6a9ae35658a3a797/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:fab6b5e37715885c69f5d091f6ff229be71e235f272ebaa35158d5a46fd548a0", size = 3924544, upload-time = "2026-02-18T16:49:41.129Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/2f/7718141485f73a924205af60041c392938852aa447a94c8cbd222ff389a1/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a4aab31bd6d1057f287c96c0effca3a25584eb9cc702f282ecb96ded7814e830", size = 4235297, upload-time = "2026-02-18T16:49:46.726Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/f9/1add717e2643a003bbde31b1b220172e64fbc0cb09f06429820c9173f7fc/psycopg_binary-3.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:59aa31fe11a0e1d1bcc2ce37ed35fe2ac84cd65bb9036d049b1a1c39064d0f14", size = 3547659, upload-time = "2026-02-18T16:49:52.999Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/0a/cac9fdf1df16a269ba0e5f0f06cac61f826c94cadb39df028cdfe19d3a33/psycopg_binary-3.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05f32239aec25c5fb15f7948cffdc2dc0dac098e48b80a140e4ba32b572a2e7d", size = 4590414, upload-time = "2026-02-18T16:50:01.441Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/c0/d8f8508fbf440edbc0099b1abff33003cd80c9e66eb3a1e78834e3fb4fb9/psycopg_binary-3.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c84f9d214f2d1de2fafebc17fa68ac3f6561a59e291553dfc45ad299f4898c1", size = 4669021, upload-time = "2026-02-18T16:50:08.803Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/05/097016b77e343b4568feddf12c72171fc513acef9a4214d21b9478569068/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e77957d2ba17cada11be09a5066d93026cdb61ada7c8893101d7fe1c6e1f3925", size = 5467453, upload-time = "2026-02-18T16:50:14.985Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/23/73244e5feb55b5ca109cede6e97f32ef45189f0fdac4c80d75c99862729d/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:42961609ac07c232a427da7c87a468d3c82fee6762c220f38e37cfdacb2b178d", size = 5151135, upload-time = "2026-02-18T16:50:24.82Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/49/5309473b9803b207682095201d8708bbc7842ddf3f192488a69204e36455/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae07a3114313dd91fce686cab2f4c44af094398519af0e0f854bc707e1aeedf1", size = 6737315, upload-time = "2026-02-18T16:50:35.106Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/5d/03abe74ef34d460b33c4d9662bf6ec1dd38888324323c1a1752133c10377/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d257c58d7b36a621dcce1d01476ad8b60f12d80eb1406aee4cf796f88b2ae482", size = 4979783, upload-time = "2026-02-18T16:50:42.067Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/6c/3fbf8e604e15f2f3752900434046c00c90bb8764305a1b81112bff30ba24/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07c7211f9327d522c9c47560cae00a4ecf6687f4e02d779d035dd3177b41cb12", size = 4509023, upload-time = "2026-02-18T16:50:50.116Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/6b/1a06b43b7c7af756c80b67eac8bfaa51d77e68635a8a8d246e4f0bb7604a/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8e7e9eca9b363dbedeceeadd8be97149d2499081f3c52d141d7cd1f395a91f83", size = 4185874, upload-time = "2026-02-18T16:50:55.97Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/d3/bf49e3dcaadba510170c8d111e5e69e5ae3f981c1554c5bb71c75ce354bb/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cb85b1d5702877c16f28d7b92ba030c1f49ebcc9b87d03d8c10bf45a2f1c7508", size = 3925668, upload-time = "2026-02-18T16:51:03.299Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/92/0aac830ed6a944fe334404e1687a074e4215630725753f0e3e9a9a595b62/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d4606c84d04b80f9138d72f1e28c6c02dc5ae0c7b8f3f8aaf89c681ce1cd1b1", size = 4234973, upload-time = "2026-02-18T16:51:09.097Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/96/102244653ee5a143ece5afe33f00f52fe64e389dfce8dbc87580c6d70d3d/psycopg_binary-3.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:74eae563166ebf74e8d950ff359be037b85723d99ca83f57d9b244a871d6c13b", size = 3551342, upload-time = "2026-02-18T16:51:13.892Z" }, +] + [[package]] name = "psycopg-pool" version = "3.3.0" @@ -4922,18 +4956,16 @@ name = "torchvision" version = "0.23.0" source = { registry = "https://download.pytorch.org/whl/cpu" } resolution-markers = [ - "python_full_version >= '3.13' and platform_machine == 'aarch64' and platform_python_implementation != 'CPython' and sys_platform == 'linux'", - "python_full_version < '3.13' and platform_machine == 'aarch64' and platform_python_implementation != 'CPython' and sys_platform == 'linux'", "python_full_version >= '3.13' and platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", "python_full_version < '3.13' and platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", "python_full_version >= '3.13' and sys_platform == 'darwin'", "python_full_version < '3.13' and sys_platform == 'darwin'", ] dependencies = [ - { name = "numpy", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, - { name = "pillow", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "numpy", marker = "(platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "pillow", marker = "(platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or sys_platform == 'darwin'" }, { name = "torch", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cpu/torchvision-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e0e2c04a91403e8dd3af9756c6a024a1d9c0ed9c0d592a8314ded8f4fe30d440" }, @@ -4953,9 +4985,9 @@ resolution-markers = [ "(python_full_version < '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.13' and platform_python_implementation != 'CPython' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", ] dependencies = [ - { name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "pillow", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "pillow", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cpu/torchvision-0.23.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ae459d4509d3b837b978dc6c66106601f916b6d2cda75c137e3f5f48324ce1da" }, @@ -5291,18 +5323,6 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, ] -[[package]] -name = "wasabi" -version = "1.1.3" -source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/f9/054e6e2f1071e963b5e746b48d1e3727470b2a490834d18ad92364929db3/wasabi-1.1.3.tar.gz", hash = "sha256:4bb3008f003809db0c3e28b4daf20906ea871a2bb43f9914197d540f4f2e0878", size = 30391, upload-time = "2024-05-31T16:56:18.99Z" } -wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/7c/34330a89da55610daa5f245ddce5aab81244321101614751e7537f125133/wasabi-1.1.3-py3-none-any.whl", hash = "sha256:f76e16e8f7e79f8c4c8be49b4024ac725713ab10cd7f19350ad18a8e3f71728c", size = 27880, upload-time = "2024-05-31T16:56:16.699Z" }, -] - [[package]] name = "volcengine-python-sdk" version = "5.0.13" @@ -5318,6 +5338,18 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/2c/38df87249e7181a8f120bc1cec84b6f7ebe3ab94ee65d7d82307117ad889/volcengine_python_sdk-5.0.13-py2.py3-none-any.whl", hash = "sha256:53fc6edc86c7665b3d0b871e8ef2db9a7c81fbce03ce2c93bf5f7a98fa6d5dbd", size = 30611404, upload-time = "2026-03-02T13:32:50.256Z" }, ] +[[package]] +name = "wasabi" +version = "1.1.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/f9/054e6e2f1071e963b5e746b48d1e3727470b2a490834d18ad92364929db3/wasabi-1.1.3.tar.gz", hash = "sha256:4bb3008f003809db0c3e28b4daf20906ea871a2bb43f9914197d540f4f2e0878", size = 30391, upload-time = "2024-05-31T16:56:18.99Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/7c/34330a89da55610daa5f245ddce5aab81244321101614751e7537f125133/wasabi-1.1.3-py3-none-any.whl", hash = "sha256:f76e16e8f7e79f8c4c8be49b4024ac725713ab10cd7f19350ad18a8e3f71728c", size = 27880, upload-time = "2024-05-31T16:56:16.699Z" }, +] + [[package]] name = "watchfiles" version = "1.1.1" @@ -5665,6 +5697,7 @@ dependencies = [ { name = "openai" }, { name = "opencv-python-headless" }, { name = "pillow" }, + { name = "psycopg", extra = ["binary"] }, { name = "pyjwt" }, { name = "pymilvus" }, { name = "pymupdf" }, @@ -5686,8 +5719,8 @@ dependencies = [ { name = "tomli-w" }, { name = "torch", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, - { name = "torchvision", version = "0.23.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, - { name = "torchvision", version = "0.23.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchvision", version = "0.23.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "torchvision", version = "0.23.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "tqdm" }, { name = "typer" }, { name = "unstructured" }, @@ -5749,6 +5782,7 @@ requires-dist = [ { name = "openai", specifier = ">=1.109" }, { name = "opencv-python-headless", specifier = ">=4.11.0.86" }, { name = "pillow", specifier = ">=10.5.0" }, + { name = "psycopg", extras = ["binary"], specifier = ">=3.2.0" }, { name = "pyjwt", specifier = ">=2.8.0" }, { name = "pymilvus", specifier = ">=2.5.8" }, { name = "pymupdf", specifier = ">=1.25.5" },