Khoj 自定义 Agent 实现指南:从 Admin 创建、权限模型到 API 与知识绑定机制
Agent(代理)是 Khoj 中将"角色设定、专属知识库、可用工具与指定聊天模型"打包成独立对话实体的核心机制。本文以官方文档 Agents 的自托管创建流程为主线,结合 Agent 数据模型、AgentAdapters 数据访问层 和 REST API 路由 的源码实现,讲透自建 Khoj 服务中 Agent 的完整配置项、可见性权限规则,以及绑定知识文件后底层如何以原子事务同步向量条目。读完后你可以:在 Django Admin 中创建面向全团队或仅面向个人的 Agent,理解其各字段的落库语义,并能通过 API 以编程方式创建、更新、删除 Agent。
1. Agent 是什么:一套可配置的对话人格
Khoj 文档对 Agent 的定义非常简洁:它允许你在 Khoj 上配置自定义系统提示词(custom system prompts)。服务器管理员(self-hosted 场景下的部署者)可以创建自己的 Agent,并控制其对所有用户是否可见。
在源码中,一个 Agent 远不止一个提示词。Agent 模型 的完整字段构成了它的全部能力面:
| 字段 | 类型 | 默认值 | 含义 |
|---|---|---|---|
creator |
ForeignKey → KhojUser | None |
创建者;为 None 时自动标记为管理员托管 |
name |
CharField(200) | — | Agent 显示名称 |
personality |
TextField | None |
自定义系统提示词,即文档中所说的核心配置项 |
input_tools |
ArrayField | [] |
该 Agent 允许的输入侧工具,可取值:general / online / notes / webpage / code(见 InputToolOptions) |
output_modes |
ArrayField | [] |
输出模式,可取值:image / diagram(见 OutputModeOptions) |
managed_by_admin |
BooleanField | False |
是否由管理员托管 |
chat_model |
ForeignKey → ChatModel | — | 该 Agent 绑定的聊天模型(必填外键) |
slug |
CharField(200),唯一 | 自动生成 | 形如 名称-6位随机数字,由 save() 钩子在首次保存时生成 |
style_color |
CharField | orange |
界面卡片颜色,共 15 种可选色值(blue/green/red 等) |
style_icon |
CharField | Lightbulb |
界面图标,共 25 种可选图标(Robot/Code/GraduationCap 等) |
privacy_level |
CharField | private |
可见性:public / private / protected |
is_hidden |
BooleanField | False |
是否对用户隐藏("隐藏 Agent"用于会话级临时人格) |
可以看到,personality 字段承载文档强调的"自定义提示词"职责,而 input_tools、output_modes、chat_model 和知识文件绑定则让每个 Agent 成为一个能力边界明确的独立助手。
2. 自托管创建 Agent:Django Admin 三步操作
这是 官方文档 给出的核心操作流程,适用于 self-hosted 部署:
- 进入管理入口:在浏览器访问你服务器上的
server/admin/database/agent,点击 Add Agent 创建新 Agent; - 配置可见性:将其设置为
public,该 Agent 即对服务器上所有用户可见;若要限制为仅特定用户可用,则不要设置public标记,并在Creator字段中填入该用户; - 写入人格:把你的自定义系统提示词填入
personality字段。
这三步之所以成立,背后是对应源码的严格支撑:
- 管理入口存在的前提是 AgentAdmin 已注册到 Django Admin(
@admin.register(Agent)),它提供id/name列表展示、按id与name搜索,以及按privacy_level过滤的侧边栏过滤器——这意味着管理员可以直接按public/private/protected三类快速筛查 Agent; - "设为 public 即可被所有用户看到"对应数据访问层 get_all_accessible_agents 的查询逻辑:返回的 Agent 集合 =
privacy_level == public且managed_by_admin == True的 Agent,或creator == 当前用户且is_hidden == False的 Agent。未登录用户只能看到前者。可以推断,Admin 中创建的 Agent 因creator为空而自动满足managed_by_admin(见下一节),所以public级别的 Admin Agent 会对全站用户生效; - "不设 public + 指定 Creator"对应私有可见性:aget_agent_by_slug 的访问控制表达式为
(privacy_level == public) | (creator == user),即私有 Agent 只有创建者本人能按 slug 取到。
注意一个字段名映射:早期文档措辞中的
public标记,在现行模型中体现为privacy_level字段(取值public/private/protected)。protected是第三档——ais_agent_accessible 显示,protected级别对登录用户判定为可访问,但不会出现在匿名用户的可访问列表中。
3. 模型层关键机制:slug 生成与 managed_by_admin 自动标记
Agent.save() 中有两段值得了解的落库行为:
def save(self, *args, **kwargs):
is_new = self._state.adding
if self.creator is None:
self.managed_by_admin = True
if is_new and not self.slug:
random_sequence = "".join(choice("0123456789") for i in range(6))
slug = f"{self.name.lower().replace(' ', '-')}-{random_sequence}"
self.slug = slug
super().save(*args, **kwargs)
creator为None⇒ 强制managed_by_admin = True。模型注释明确写道:Creator will only be null when the agents are managed by admin。这正是 Admin 后台创建的 Agent 能进入"全用户可见"白名单的原因;- slug 自动派生:首次保存时若未指定 slug,则以
名称小写连字符化 + 6 位随机数字生成唯一 slug(例如research-assistant-482913)。slug 是 API 层寻址 Agent 的主键,测试 test_create_default_agent 也验证了默认 Agent 创建后input_tools == []、privacy_level == public、managed_by_admin为真。
4. 默认 Agent:每个 Khoj 实例的"零号 Agent"
除手工创建的 Agent 外,每个部署实例都有一个内建默认 Agent,由 create_default_agent 维护:
- 名称固定为
Khoj,slug 固定为khoj(常量DEFAULT_AGENT_NAME/DEFAULT_AGENT_SLUG); - 其
personality取自 prompts.personality 模板(运行时填充当前日期与星期),聊天模型取服务级默认模型; - 创建时会把库中所有
agent=None的历史会话回填挂到默认 Agent 下,保证会话与 Agent 的关联完整性; - 特殊之处:get_agent_chat_model 对默认 Agent 是动态解析模型(跟随用户/服务级默认设置),而其它 Agent 返回其静态绑定的
chat_model。
这也解释了 API 返回结构里为什么默认 Agent 会被单列处理:all_agents 将默认 Agent 固定排在列表首位,其余 Agent 按"最近两周会话活跃时间"降序排序,未使用过的随机打散。
5. REST API:以编程方式管理 Agent
自托管场景下,除 Admin 界面外,Khoj 通过 api_agents 路由 提供完整的 Agent CRUD。关键请求体定义在 ModifyAgentBody:
class ModifyAgentBody(BaseModel):
name: str
persona: str # 即 personality,自定义系统提示词
privacy_level: str
icon: str
color: str
chat_model: str
files: Optional[List[str]] = [] # 绑定知识文件的文件路径列表
input_tools: Optional[List[str]] = []
output_modes: Optional[List[str]] = []
slug: Optional[str] = None
is_hidden: Optional[bool] = False
主要端点一览(均来自 api_agents.py):
| 方法 + 路径 | 鉴权 | 说明 |
|---|---|---|
GET /agents |
可匿名 | 列出当前用户可访问的全部 Agent(含默认 Agent 置顶、近两周会话排序) |
GET /agents/{agent_slug} |
可匿名 | 按 slug 获取只读 Agent 详情 |
POST /agents |
登录 | 创建 Agent;若 chat_model 免费档不满足订阅权限则降级 |
PATCH /agents |
登录 | 按 slug 更新 Agent |
DELETE /agents/{agent_slug} |
登录 | 删除 Agent(仅创建者可删,见下) |
GET /agents/options |
可匿名 | 返回所有 input_tools/output_modes 取值及对应描述文案 |
POST/PATCH /agents/hidden |
登录 | 创建/更新会话级"隐藏 Agent"(临时人格) |
几个源码级的行为细节值得注意:
- 提示词安全检查:
POST/PATCH都会先调用acheck_if_safe_prompt(body.persona, user, lax=...)校验 persona 是否安全,且私有(privacy_level == private)Agent 使用更宽松(lax=True)的校验标准——因为私有 Agent 只影响自己; - 模型档位控制:创建时若所选
chat_model的price_tier非 FREE 且用户无premium订阅,agent_chat_model会被置为None(回退默认模型),见 create_agent; - 删除保护:adelete_agent_by_slug 校验
agent.creator != user则拒绝删除,并会连带清空该 Agent 名下的全部Entry(知识条目)。
GET /agents/options 端点还暴露了 input_tools 与 output_modes 的人类可读描述(来自 command_descriptions_for_agent / mode_descriptions_for_agent 映射),前端 Agents 配置页即基于此渲染可选项。
6. Agent 知识绑定:原子事务如何复制向量条目
创建 Agent 时可同时传入 files 参数,将用户已有知识文件"挂"到该 Agent 名下,形成专属知识库。真实的数据操作在 atomic_update_agent(注意 @transaction.atomic 装饰器)中完成,流程是:
update_or_create按(slug, creator)定位并落库 Agent 全部字段;- 删除该 Agent 名下旧的
FileObject与Entry; - 从创建者个人的
FileObject/Entry(agent=None)中筛选file_name__in=files的记录,bulk_create(FileObject 批 100、Entry 批 500)复制出新的、带agent指向的条目——注意向量(embeddings)是直接复制引用,不重新编码; - 整段操作在同一事务内,失败整体回滚,避免"部分文件同步"的脏状态。
test_agents.py 中的测试用例印证了这条链路的正确性:
- test_create_or_update_agent_with_knowledge_base:绑定
tests/data/markdown/having_kids.markdown后,Entry.objects.filter(agent=new_agent)恰好只有该文件的条目; - test_agent_with_knowledge_base_and_search:绑定后对该 Agent 执行
execute_search(user, q="having kids", agent=new_agent)能命中 5 条结果,证明 Agent 级检索路径生效; - test_agent_with_knowledge_base_and_search_not_creator_and_private:私有 Agent 对非创建者用户检索返回 0 条,与
privacy_level访问控制完全一致;而public级别的 Agent 非创建者也能检索到(test_agent_with_knowledge_base_and_search_not_creator); - test_large_knowledge_base_atomic_update 与 test_concurrent_agent_updates_atomicity 用 180+ 文件的压力场景验证:大批量文件一次性替换以及并发更新时,
FileObject与Entry数量严格一致、无部分同步残留——这是该原子事务设计最直接的回归保障。
7. 小结与延伸阅读
Khoj 的 Agent 机制可以概括为一条主线:personality 定义"它是谁",chat_model + input_tools + output_modes 定义"它能做什么",files 定义"它知道什么",privacy_level + creator 定义"谁能用它"。自托管用户最常用的是 Admin 路径(server/admin/database/agent → Add Agent → 设置 public 与否 → 填写 personality),而集成方则应通过 /agents REST 端点完成同样的事情。
继续深入时,可按以下路径阅读源码:
- Agent 模型定义:全部字段与
save()钩子; - AgentAdapters:可见性查询、默认 Agent、原子更新;
- api_agents 路由:端点与请求体、提示词安全校验;
- AgentAdmin 注册:管理后台的展示与过滤配置;
- test_agents.py:创建、权限、知识绑定与原子性的测试证据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
