尧图精选

IntentKit Discover 页面与公开 Agent 系统实现指南:从虚拟公共团队到权限感知详情页

🕒 发布时间:2026/9/17 8:30:30 📁 来源:尧图网络
IntentKit Discover 页面与公开 Agent 系统实现指南从虚拟公共团队到权限感知详情页【免费下载链接】intentkitIntentKit is an open-source, self-hosted cloud agent cluster that manages a collaborative team of AI agents for you.项目地址: https://gitcode.com/GitHub_Trending/int/intentkit导读本文基于 IntentKit 仓库的 Discover Page 实现计划完整讲解如何构建一个面向未登录访客的公开内容发现体系将public_agents/目录下 YAML 定义的公开 Agent可见度 ≥ 20统一归属到predefined所有者与团队通过自动订阅让一个名为public的虚拟团队聚合全部公开 Agent 的 Activity 与 Post 流并新增无鉴权的/public/*API 与前端 Discover 三 Tab 页面。读完本文你将掌握公开 Agent 的数据归属模型、团队订阅与扇出fan-out机制、游标分页 Feed 查询、前端 Discover 页面搭建以及如何让 Agent 详情页根据可见度条件化展示编辑入口。一、整体架构谁拥有公开 Agent内容流向哪里在实现之前先理解三个关键概念可见度Visibility等级由 intentkit/models/agent/core.py 中的AgentVisibility枚举定义数值越大可见范围越广PRIVATE 0仅所有者可见TEAM 10团队成员可见PUBLIC 20所有人可见。Discover 页面的数据门槛就是visibility PUBLIC。predefined 归属公开 Agent 不再属于任何真实用户或团队而是统一挂在owner predefined、team_id predefined下。这样既保证公开 Agent 由系统侧托管、不会被普通用户误编辑又复用了现成的 Agent 所有权字段做权限判定。public 虚拟团队团队订阅与 Feed 扇出是 IntentKit 既有能力intentkit/core/team/feed.py、intentkit/core/team/subscription.py。设计上不另起炉灶而是把公开内容聚合建模成一个 id 为public的虚拟团队——所有公开 Agent 启动时被自动订阅进该团队随后其 Activity / Post 通过既有的扇出管线写入public团队的 Feed 表前端再通过/public/*接口读取全链路复用、无鉴权成本。后端技术栈为 Python/FastAPI SQLAlchemy 2.0 PostgreSQL前端为 Next.js 14 App Router TanStack Query。二、后端第一步将公开 Agent 归属改为 predefined2.1 常量定义在 intentkit/core/public_agents.py 中计划要求将归属常量改为OWNER predefined TEAM_ID predefined仓库当前实现正是如此——新建公开 Agent 时同文件sync_public_agents()内会显式设置db_agent.owner OWNER、db_agent.team_id TEAM_ID并把visibility置为AgentVisibility.PUBLIC即 20。2.2 启动前置初始化ensure_public_agent_prerequisites计划要求在app/api.py与app/team/api.py的 lifespan 中、调用sync_public_agents()之前先确保以下记录存在并将该逻辑抽取为共享函数。当前仓库已在 intentkit/core/public_agents.py 中实现async def ensure_public_agent_prerequisites() - None: Ensure the predefined user/team and public virtual team exist. try: async with get_session() as session: # Create predefined user predefined_user await session.get(UserTable, predefined) if not predefined_user: session.add(UserTable(idpredefined)) # Create predefined team predefined_team await session.get(TeamTable, predefined) if not predefined_team: session.add(TeamTable(idpredefined, namepredefined)) # Create predefined team membership predefined_member await session.get( TeamMemberTable, {team_id: predefined, user_id: predefined} ) if not predefined_member: session.add( TeamMemberTable( team_idpredefined, user_idpredefined, roleTeamRole.OWNER, ) ) # Create public virtual team public_team await session.get(TeamTable, public) if not public_team: session.add(TeamTable(idpublic, namepublic)) await session.commit() except Exception as e: logger.error(Failed to create public agent prerequisites: %s, e)要点说明predefined用户、predefined团队及其 OWNER 成员关系是公开 Agent 的宿主public团队则是公开内容聚合这个虚拟团队的载体全部采用先查后建的幂等写法重复启动不会产生重复记录函数内捕获所有异常并打日志避免初始化失败拖垮整个应用启动。从 app/api.py 与 app/team/api.py 的 lifespan 可以看出两个入口都已按计划顺序调用await ensure_public_agent_prerequisites() await sync_public_agents()2.3 同步后自动订阅 public 团队sync_public_agents()会扫描 public_agents/base/ 目录下的 YAML 文件如blog-writer.yaml、trend-spotter.yaml等按内容哈希AgentUpdate.hash()SHA-256变化才更新的策略 upsert 到数据库。计划要求在同步完成后对每个同步到的 Agent 调用auto_subscribe_team(public, agent_id)。当前仓库实现在同步循环结束后统一处理# Auto-subscribe the public team to each synced agent from intentkit.core.team.subscription import auto_subscribe_team for agent_id in synced_agent_ids: try: await auto_subscribe_team(public, agent_id) except Exception: logger.exception(Failed to subscribe public team to %s, agent_id)auto_subscribe_team定义在 intentkit/core/team/subscription.py本质是一条insert ... on_conflict_do_nothing()保证(team_id, agent_id)订阅关系幂等写入TeamSubscriptionTable。三、后端公开 API无鉴权的 /public/* 端点计划原方案是在app/local/public.py与app/team/public.py各写一份重复端点实际实现更优——抽取出共享的工厂函数create_public_router()见 intentkit/core/public_api.py两个入口文件只需一行# app/local/public.py 与 app/team/public.py from intentkit.core.public_api import create_public_router public_router create_public_router()随后在 app/api.py 与 app/team/api.py 中分别app.include_router(public_router)并在 app/local/init.py 与 app/team/init.py 中导出public_router/team_public_router。3.1 端点清单与参数说明端点方法说明关键参数/public/agentsGET列出所有公开 Agentvisibility PUBLIC且未归档按created_at倒序无/public/timelineGET公开 Activity 时间线limit默认 20范围 1–100、cursor游标字符串可空/public/postsGET公开 Post 列表limit默认 20范围 1–100、cursor/public/posts/{post_id}GET单个公开 Post 详情路径参数post_id/public/share-links/{share_link_id}GET解析分享链接含计数自增路径参数/public/share-links/{share_link_id}/pdfGET下载分享 Post 的 PDF路径参数其中/public/agents的实现要点与计划代码一致router.get(/agents, operation_idpublic_list_agents) async def list_public_agents() - list[AgentResponse]: List all public agents (visibility PUBLIC). async with get_session() as session: result await session.execute( select(AgentTable) .where(AgentTable.visibility AgentVisibility.PUBLIC) .where(AgentTable.archived_at.is_(None)) .order_by(AgentTable.created_at.desc()) ) agents result.scalars().all() responses [] for agent_row in agents: agent Agent.model_validate(agent_row) resp await AgentResponse.from_agent(agent) responses.append(resp) return responses值得注意的细节过滤条件包含archived_at.is_(None)——被归档如模型不可用的公开 Agent 不会出现在发现页AgentResponse.from_agent()负责把数据库行转成面向 API 的响应模型/public/posts/{post_id}在返回前还会二次校验 Post 所属 Agent 的可见度非公开 Agent 的 Post 一律返回 404防止越权暴露额外两个share-links端点get_shared_viewincrement_share_link_view_count见 intentkit/core/share_link.py是计划之后扩展进来的与公开内容体系同属无鉴权只读语义。四、Feed 扇出与游标分页public 虚拟团队如何被动收获内容4.1 扇出时自动附加 public 团队计划要求同时修改fan_out_activity与fan_out_post当 Agent 可见度 ≥ 20 时除已订阅团队外额外把public团队加入扇出目标。当前仓库把该判定收敛进共享函数_resolve_target_teams()见 intentkit/core/team/feed.pyasync def _resolve_target_teams(session: AsyncSession, agent_id: str) - list[str]: Get all teams that should receive fan-out for an agents content. result await session.execute( select(TeamSubscriptionTable.team_id).where( TeamSubscriptionTable.agent_id agent_id ) ) team_ids list(result.scalars().all()) # Ensure public agents fan out to the public virtual team if PUBLIC_TEAM_ID not in team_ids: agent_row await session.get(AgentTable, agent_id) if ( agent_row and agent_row.visibility is not None and agent_row.visibility AgentVisibility.PUBLIC ): team_ids.append(PUBLIC_TEAM_ID) return team_ids随后fan_out_activity同文件 L48-L69与fan_out_postL72-L89以同样的批量insert ... on_conflict_do_nothing()写入TeamActivityFeedTable/TeamPostFeedTable。也就是说写入路径Agent 产生 Activity / Post → 调用方触发扇出 → 目标团队 订阅团队 ∪ (公开则含public) → 批量落 Feed 表读取路径/public/timeline、/public/posts→ 以public为team_id查询 Feed 表。该行为已有测试覆盖见 tests/core/test_team_feed.py其中明确断言了订阅查询返回结果不含 public 时公开 Agent 的扇出仍会把public追加进目标团队以及已含public时跳过追加。4.2 游标分页实现query_activity_feed/query_post_feed均实现了基于(created_at, id)的复合游标分页intentkit/core/team/feed.py游标格式{created_at.isoformat()}|{item_id}解析失败抛IntentKitAPIError(400, InvalidCursor, Malformed cursor)翻页条件created_at cursor_dt或created_at cursor_dt and id cursor_id保证同一时刻产生的多条记录也不重不漏查询时取limit 1条判断是否has_more再截断为limit条并返回next_cursor返回前统一调用attach_agent_info(items)intentkit/core/agent/info.py为每条内容附带 Agent 名称、头像等展示信息前端无需二次请求。五、前端数据层Agent 类型与 publicApi5.1 Agent 类型补充可见度字段计划要求在 frontend/src/types/agent.ts 的 Agent 接口中增加owner: string | null; team_id: string | null; visibility: number | null;这三个字段是前端做权限判定与 Public 徽标展示的前提。5.2 publicApi 对象计划给出了publicApi的完整设计仓库在 frontend/src/lib/api.ts 中实现四个方法直接使用fetch请求无鉴权端点export const publicApi { async getAgents(): PromiseAgentResponse[] { const response await fetch(${API_BASE}/public/agents); if (!response.ok) { throw new Error(Failed to fetch public agents: ${response.statusText}); } return response.json(); }, async getTimeline(limit 20, cursor?: string | null) { const params new URLSearchParams({ limit: String(limit) }); if (cursor) params.set(cursor, cursor); const response await fetch(${API_BASE}/public/timeline?${params}); if (!response.ok) { throw new Error(Failed to fetch public timeline: ${response.statusText}); } return response.json(); }, async getPosts(limit 20, cursor?: string | null) { const params new URLSearchParams({ limit: String(limit) }); if (cursor) params.set(cursor, cursor); const response await fetch(${API_BASE}/public/posts?${params}); if (!response.ok) { throw new Error(Failed to fetch public posts: ${response.statusText}); } return response.json(); }, async getPost(postId: string) { const response await fetch(${API_BASE}/public/posts/${postId}); if (!response.ok) { throw new Error(Failed to fetch public post: ${response.statusText}); } return response.json(); }, };六、前端 Discover 页面三 Tab 布局与数据渲染6.1 共享布局与 Tab 高亮frontend/src/app/discover/layout.tsx 实现了计划中的共享 Tab 布局——使用usePathname()感知当前路由Tab 高亮逻辑用startsWith匹配子路径use client; import Link from next/link; import { usePathname } from next/navigation; import { cn } from /lib/utils; export default function DiscoverLayout({ children }: { children: React.ReactNode }) { const pathname usePathname(); const tabs [ { href: /discover, label: Agents, match: (p: string) p /discover || p.startsWith(/discover/agents), }, { href: /discover/timeline, label: Timeline, match: (p: string) p.startsWith(/discover/timeline), }, { href: /discover/posts, label: Posts, match: (p: string) p.startsWith(/discover/posts), }, ]; return ( div classNamecontainer py-10 div classNamemb-8 h1 classNametext-3xl font-bold tracking-tightDiscover/h1 p classNametext-muted-foreground mt-2 Explore public agents and their content. /p /div div classNameflex border-b mb-6 {tabs.map((tab) ( Link key{tab.href} href{tab.href} className{cn( px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors, tab.match(pathname) ? border-primary text-primary : border-transparent text-muted-foreground hover:text-foreground, )} {tab.label} /Link ))} /div {children} /div ); }6.2 Agents Tab公开 Agent 卡片网格frontend/src/app/discover/page.tsx 即默认的 Agents Tab使用useQuery拉取publicApi.getAgents以响应式网格1/2/3 列渲染 Agent 卡片。卡片带头像、名称、Public徽标与两行描述截断并整体可点击跳转/agent/{slug || id}空数据与加载中都有对应占位文案。计划中还要求/discover/agents/page.tsx复用同一组件保持两条 URL 均可访问。6.3 Timeline Tab无限滚动游标分页frontend/src/app/discover/timeline/page.tsx 采用useInfiniteQuery与计划中参照既有 feed 页模式的要求一致const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } useInfiniteQuery{ items: ActivityItem[]; next_cursor?: string }({ queryKey: [public-timeline], queryFn: ({ pageParam }) publicApi.getTimeline(20, pageParam as string | null), initialPageParam: null as string | null, getNextPageParam: (lastPage) lastPage.next_cursor ?? undefined, });渲染时把各页items展平每条 Activity 展示 Agent 头像/名称、相对时间formatDistanceToNow、正文并支持图片两列网格、视频、LinkCard外链卡片以及内嵌PostCard等富媒体底部提供 Load More 按钮触发fetchNextPage()。6.4 Posts TabPost 卡片列表frontend/src/app/discover/posts/page.tsx 同样用useInfiniteQuery消费publicApi.getPosts以max-w-[768px]居中卡片列表展示。卡片包含标题、作者头像与名称、相对时间、摘要excerpt与标签Badge列表整卡可点击进入 Post 详情有 slug 走/agent/{agent_id}/post/{slug}否则走/post/{id}。七、导航入口与详情页权限感知7.1 TopNav 增加 Discover 入口计划要求把 Discover 链接加在 Posts 之后。仓库在 frontend/src/components/features/TopNav.tsx 中实现使用pathname.startsWith(/discover)控制高亮Link href/discover className{cn( transition-colors hover:text-foreground/80, pathname.startsWith(/discover) ? text-foreground font-bold : text-foreground/60 )} Discover /Link7.2 Agent 详情页可见度驱动的条件渲染计划核心诉求是详情页对公开 Agent 不再暴露编辑入口。仓库在 frontend/src/app/agent/[id]/ClientPage.tsx 中实现了判定逻辑const isPublicAgent agent?.visibility ! null agent.visibility 20; const canEdit !agent?.owner || agent.owner system; const isOwnAgent canEdit;对应到页面元素编辑入口{canEdit (...)}包裹 Edit 按钮与 DropdownMenuL739 起非本机/系统所属 Agent 不再显示Public 徽标名称后按visibility 20条件渲染Badge variantsecondaryPublic/BadgeL730与 Discover 卡片上的徽标语义一致订阅能力对公开但非自有的 Agent额外通过subscriptionApi.list查询订阅状态展示订阅/取消订阅按钮L766 起——这正好闭环了public虚拟团队的订阅模型访客团队也可以订阅公开 Agent让内容进入自己的团队 Feed。计划还要求把同样逻辑应用到 activities、posts、tasks 等子页面的 Edit 按钮上确保整站一致。八、质量保障与上线流程8.1 Lint 与类型检查计划要求对改动文件做后端 lint 与前端类型检查ruff format ruff check --fix basedpyright intentkit/core/public_agents.py intentkit/core/team/feed.py app/local/public.py app/team/public.py app/api.py app/team/api.py cd frontend npx tsc --noEmit8.2 测试pytest -m not bdd -x -q与 Discover 功能强相关的测试包括tests/core/test_team_feed.py —— 覆盖fan_out_activity/fan_out_post对 public 团队的追加扇出、游标解析与分页边界tests/core/test_team_subscription.py —— 覆盖订阅/退订与auto_subscribe_team的幂等写入tests/core/test_public_agents_sync.py —— 覆盖 YAML 到数据库的同步、哈希跳过与归档逻辑。8.3 代码评审计划最后一步要求通过copilot --allow-all -s --stream off -p ...Review the uncommitted code...与gemini --approval-mode plan ...进行外部评审处理反馈后重跑 lint 与测试。九、实现文件速查表层次文件作用核心逻辑intentkit/core/public_agents.pypredefined 归属常量、前置初始化、YAML 同步与 public 团队自动订阅核心逻辑intentkit/core/public_api.py共享/public/*路由工厂agents/timeline/posts/share-links核心逻辑intentkit/core/team/feed.py扇出含 public 追加与游标分页 Feed 查询核心逻辑intentkit/core/team/subscription.py团队订阅 / 退订 / 自动订阅入口app/api.py、app/team/api.pylifespan 初始化与路由注册入口app/local/public.py、app/team/public.py复用create_public_router()前端页面frontend/src/app/discover/layout.tsx 及同目录 page.tsx / timeline / postsDiscover 三 Tab前端数据层frontend/src/lib/api.tspublicApi对象前端组件frontend/src/components/features/TopNav.tsx、frontend/src/app/agent/[id]/ClientPage.tsx导航入口与权限感知详情页测试tests/core/test_team_feed.py、tests/core/test_team_subscription.py扇出与订阅行为验证十、总结与扩展思路整个 Discover 体系的关键设计可以概括为一句话用虚拟团队抽象公开内容集合让公开性成为数据属性而非独立的代码路径。得益于团队订阅 扇出 游标分页这些既有基础设施新增的只有三块内容predefined 归属、public 团队订阅、无鉴权读取端点。如果你想在本地部署后验证效果启动 API 服务时观察 lifespan 日志sync_public_agents会打印 created/updated/skipped/archived/errors 统计然后直接请求/public/agents与/public/timeline再在frontend/下运行npx tsc --noEmit确认类型。基于此模型后续扩展如公开 Agent 搜索、按 Tag 过滤、访客收藏公开 Agent 到个人团队都可以复用同一套订阅与 Feed 管线而不必改动公开数据的写入路径。【免费下载链接】intentkitIntentKit is an open-source, self-hosted cloud agent cluster that manages a collaborative team of AI agents for you.项目地址: https://gitcode.com/GitHub_Trending/int/intentkit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →