OpenHuman 定时任务系统深度解析Cron 模块的调度、投递与重试机制【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhumanOpenHuman 的cron模块是内置的定时任务运行时Scheduled-job runtime负责 cron 表达式与人类化时延如5m、2h的解析、作业与运行记录的持久化、到点触发shell/agent类型作业的轮询调度器以及把作业结果发布到 agent / 渠道channel管线的事件投递层。它不拥有 agent 的实际执行那属于agent::triage也不拥有 shell 沙箱那属于security::SecurityPolicy。本文基于 cron 模块说明 与模块源码完整讲解其数据模型、调度循环、投递模式与对外接口RPC 与 agent 工具帮助你在 OpenHuman 中创建、运维定时任务并理解其底层的容错设计。一、模块职责与模块结构cron 模块说明 给出的一句话定义是cron 模块owns cron-expression and human-delay parsing, the persistent job run store, the polling scheduler that fires due jobs (shellandagenttypes), and the delivery layer that publishes events into the agent / channel pipelines。从源码结构看模块按职责拆分为如下文件见 mod.rs文件职责types.rs持久化作业模型CronJob/CronJobPatch/CronRun/JobType/Schedule/SessionTarget/DeliveryConfigschedule.rscron 表达式解析、时区处理、active_hours 窗口、下一次运行时间计算与校验store.rs基于 SQLiterusqlite的作业与运行记录存储ops.rsRPC 操作层cron.{add, list, update, remove, run, runs}scheduler.rs主轮询循环、作业执行、重试与投递pub mod scheduler含pub async fn run(config: Config)与deliver_jobseed.rs首次启动时安装内置作业每日晨报等bus.rs事件总线订阅者CronDeliverySubscriber把投递请求路由到具体渠道schemas.rsJSON-RPC controller schema 注册all_cron_controller_schemas/all_cron_registered_controllerstools/暴露给 agent 的cron_add、cron_list、cron_update、cron_remove、cron_run、cron_runs工具实现其调用出去的依赖关系来自模块说明均可在源码中印证src/openhuman/agent/agent类型作业通过agent::triage的触发路径执行src/openhuman/security/shell 作业在写入前与执行前都会经过SecurityPolicy::from_config的命令白名单校验src/openhuman/config/Config提供轮询间隔、工作区目录与自治autonomy策略src/openhuman/platform/health/调度器启动时注册健康订阅者src/openhuman/channels/投递事件可扇出到各渠道src/core/事件总线init_global与publish_global(DomainEvent::Cron(*))。被调用方则包括 schedule 工具把 cron 操作暴露给 agent、controller 注册表接线all_cron_*以及通过事件总线消费Cron事件的渠道与 agent 运行时。二、数据模型CronJob、Schedule 与 DeliveryConfig2.1 作业类型与会话目标JobType枚举定义在 types.rs含三种类型README 列举的shell与agent以及源码中额外存在的flowshell默认执行一条 shell 命令agent运行一次 agent 回合flow绑定一条flows::Flow的调度触发。其command列存放 flow id触发时调度器发布DomainEvent::FlowScheduleTick { flow_id }事件由flows::bus::FlowTriggerSubscriber完成实际派发见 types.rs 中Flow变体的注释与 scheduler 实现。该类型由flows::ops::flows_set_enabled经cron::add_flow_schedule_job创建agent 的cron_add工具不能创建它。SessionTarget决定 agent 作业在哪个会话中运行isolated默认独立会话或main主会话。2.2 Schedule三种调度形式与裸字符串兼容Schedule枚举序列化为内部 tagged 对象{kind: cron, ...}支持三种形式// 1) 周期 cron 表达式可带 IANA 时区与活跃时间窗 { kind: cron, expr: 0 9 * * 1, tz: America/Los_Angeles, active_hours: { start: 09:00, end: 17:00 } } // 2) 一次性 UTC 时刻 { kind: at, at: 2026-09-10T07:00:00Z } // 3) 固定毫秒间隔 { kind: every, every_ms: 300000 }值得注意的是 types.rs 中手写的Deserialize实现裸字符串形式也会被接受。当调用方直接传schedule: 0 9 * * 1时visit_str会把它强制转换为Schedule::Cron { expr, tz: None, active_hours: None }。源码注释说明这是为了兼容 agent 与旧版前端直接传裸表达式的习惯避免线上出现 invalid type: string, expected internally tagged enum Schedule 的反序列化错误。这是一个典型的向前兼容 防御式解析设计。active_hoursActiveHours { start, end }HH:MM格式用于限制作业只在本地时段的窗口内触发跨午夜窗口如22:00–06:00在 schedule.rs 的ActiveWindow::contains中用time start || time end正确处理。2.3 CronJob 与 CronJobPatch 字段一览CronJobtypes.rs的关键字段字段类型说明idStringUUIDexpressionString冗余存储的 cron 表达式非 cron 调度时为空scheduleSchedule完整调度定义JSON 序列化落库commandStringshell 命令flow类型为 flow idpromptOptionStringagent 提示词nameOptionString人类可读名称如morning_briefingjob_type/session_target枚举作业类型与会话目标modelOptionString指定模型agent_idOptionString内置 agent 定义 ID如welcome、morning_briefing设置后调度器从注册表解析该定义用其提示词、工具白名单、迭代上限与模型提示运行profile_idOptionStringagent profile ID设置且 profile 仍存在时运行继承该 profile 的 SOUL、记忆范围、工作区描述符与白名单profile 被删除时仅告警、不带 profile 运行不会导致作业失败enabledbool是否启用暂停/恢复即改写此字段deliveryDeliveryConfig投递配置delete_after_runbool运行后自删除一次性任务created_at/next_run/last_run/last_status/last_output时间/字符串调度与最近一次运行的状态快照CronJobPatch用于增量更新。源码中有一个精妙的细节agent_id与profile_id字段采用OptionOptionString双重 Option 语义并配合自定义反序列化函数deserialize_double_optiontypes.rs线上报文解析结果语义key 缺失None不修改key 存在且为nullSome(None)清除该值key 存在且有值Some(Some(v))设置新值因为 serde 默认的OptionOptionT会把缺失和null都折叠成外层None导致{profile_id: null}线上清除被静默当成不修改。这段注释是理解可清空字段补丁模式的好例子。DeliveryConfig字段与默认值pub struct DeliveryConfig { pub mode: String, // 默认 noneDefault 实现 pub channel: OptionString, // announce 模式必填 pub to: OptionString, // announce 模式必填 pub best_effort: bool, // 默认 true }注意DeliveryConfig::default()的mode是none静默但agent 作业的默认投递是proactive——这是在add_agent_job路径上设定的见第五节cron_add工具说明二者不矛盾。三、调度计算表达式归一化、时区与 active_hoursschedule.rs 提供四个公开函数经 mod.rs 重导出normalize_expression(expr) - String把 5 字段的标准 crontab 语法分 时 日 月 周归一化为 6 字段补0秒字段6 或 7 字段含秒、可选年的 crate 原生语法原样保留其他字段数报错Invalid cron expression: {expression} (expected 5, 6, or 7 fields, got {field_count})。next_run_for_schedule(schedule, from) - DateTimeUtc计算下一次运行时间。validate_schedule(schedule, now) - Result()作业创建/更新时的入口校验。schedule_cron_expression(schedule) - OptionString从调度对象中提取裸表达式。3.1 三种调度的 next-run 语义next_run_for_schedule的分支逻辑schedule.rsCron归一化表达式 → 用croncrate 解析 → 按 IANA 时区chrono_tz缺省为设备本地时区计算下一次本地触发点并转回 UTC若配置了active_hours逐候选检查本地时刻是否落在窗口内窗口外的候选被推进到再下一个最多尝试 100,000 次后放弃并报错No future occurrence found within active hours after 100,000 attempts。At直接返回at时刻本身。Everyevery_ms必须 0结果为from every_ms并对DateTime溢出做检查。3.2 validate_schedule 的三条校验规则作业创建时add_shell_job/add_agent_job等入口都会先调用validate_scheduleCron表达式可归一化可解析、active_hours时间格式合法、时区名是合法 IANA 时区且从当前时刻能算出未来触发点Atat must be in the future——一次性时刻必须是未来时间Everyevery_ms must be 0。3.3 人类化时延解析ops 层还提供parse_human_delayops.rs把5m、2h、30s、3d解析为chrono::Duration不带单位时默认按分钟非法单位报unsupported delay unit {unit}, use s/m/h/d。它被add_once使用——add_once(config, delay, command)等价于add_once_at(config, now delay, command)即创建一个Schedule::At的 shell 一次性作业。四、持久化存储SQLite 表与输出截断store层store.rs主体在store_part_01.rs基于rusqlite::Connection读写cron_jobs表与运行记录表。关键实现事实输出截断MAX_CRON_OUTPUT_BYTES 16 * 1024超过 16 KB 的输出落库时截断并追加\n...[truncated]标记防止单条运行记录无限膨胀创建 shell 作业add_shell_job先生成 UUID、validate_schedule校验、预计算next_run再单条INSERT INTO cron_jobs (id, expression, command, schedule, job_type, prompt, name, session_target, model, enabled, delivery, delete_after_run, created_at, next_run)公开函数面README 列举均在 mod.rs 重导出add_job/add_agent_job/add_agent_job_with_definition/add_shell_job/add_flow_schedule_job/due_jobs/get_job/list_jobs/list_runs/record_last_run/record_run/remove_job/reschedule_after_run/update_job以及去重/清理辅助函数dedup_named_jobs、clear_all_jobs、delete_queued_runs、find_flow_schedule_job。due_jobs(config, now)是调度循环的取数入口返回所有enabled且next_run now的作业。五、RPC 操作层cron.{add, list, update, remove, run, runs}ops.rs 是 README 中pub use ops as rpc的实体schemas.rs 将其注册为六个 JSON-RPC controller。所有查询类操作都先检查config.cron.enabled为false时统一返回cron is disabled by config (cron.enabledfalse)。5.1 各操作的语义cron.list列出全部作业cron.update接受job_id 完整CronJobPatch若 patch 含command先经SecurityPolicy::from_config(config.autonomy, config.workspace_dir, config.action_dir)的is_command_allowed校验被拦截时报Command blocked by security policy: {cmd}cron.remove删除作业返回{ job_id: ..., removed: true }cron.runRun Now 手动触发见下节cron.runs读取运行历史limit缺省 20limit.unwrap_or(20).max(1)返回VecCronRunid、job_id、started_at、finished_at、status、output、duration_ms。ops 层还暴露四个非 controller 的辅助函数mod.rs 第 17 行重导出add_once/add_once_at/parse_human_delay/pause_job/resume_job/update_cron_job。其中pause_job/resume_job本质是update_job加CronJobPatch { enabled: Some(false/true), .. }update_cron_job兼容旧 CLI 语义expression与tz会和现有Schedule::Cron字段合并只改时区不会丢表达式active_hours始终原样保留对非 cron 调度改表达式/时区会报Cannot update expression/tz on a non-cron schedule同样在改command时做安全策略校验。5.2 cron.run 的并发防护与queued 占位cron_runops.rs的实现值得逐行读它解决了三个工程问题同一作业禁止并发执行全局ACTIVE_RUNS: LazyMutexHashSetString在 spawn 前插入job_id已存在则直接返回cron job {job_id} is already running。清理用 RAII 的ActiveRunGuard——其Drop实现保证正常完成、panic、future 取消三种路径下都会把job_id移出去一个挂死的后台任务永远不会永久锁死某个 job_id。RPC 立即返回执行被tokio::spawn到后台任务handler 立刻返回{ job_id, status: queued }。前端可观测性spawn 前先record_run(..., queued, ...)插入一条queued占位运行记录让前端轮询在 RPC 返回的瞬间就能看到状态后台任务完成后先delete_queued_runs清掉占位行再写入真实结果status为ok/error、duration_ms、output并record_last_run更新作业快照。手动运行也会走与调度循环完全相同的投递路径cron::scheduler::deliver_job因此Run Now同样会推送 proactive 消息与告警。六、轮询调度器主循环、健康信号与错误分类6.1 轮询参数scheduler::run(config: Config)scheduler.rs 的pub async fn run是主循环入口先crate::core::bus::init()确保全局事件总线已初始化重复调用是 no-op再register_health_subscriber()轮询间隔poll_secs config.reliability.scheduler_poll_secs.max(MIN_POLL_SECONDS)其中MIN_POLL_SECONDS 5之后进入loop { interval.tick().await; tick_once(...).await }。对应配置项config/schema/runtime.rs配置项默认值说明scheduler_poll_secs15轮询间隔秒运行时强制最小 5 秒scheduler_retries见default_scheduler_retries()失败重试次数provider_backoff_ms500重试退避基数运行时max(200)cron_add工具的 description 里也明确写给模型The scheduler polls on an interval (default 15s, minimum 5s) and does not catch up missed runs——轮询模型不补跑错过的触发这是使用上的重要前提。6.2 tick_once只在状态迁移时发健康事件单个轮询周期被抽成tick_once便于测试驱动其健康信号设计是迁移式transition-only发射due_jobs数据库读取失败 → 发布HealthChanged { component: scheduler, healthy: false }但连续失败只发一次last_emitted_health追踪避免长故障期间事件风暴读取成功且上次不是 healthy → 发布healthy: true这是恢复信号哪怕只是空闲 tick也证明 DB 可读写Docker 健康检查能自动从 503 恢复作业失败在process_due_jobs内把组件翻回healthy: false下一个成功的 tick 再翻回true实现自动恢复。6.3 执行、重试与永久错误识别process_due_jobs对每个到期作业调用execute_job_with_retry(config, security, job)scheduler 实现。重试循环的要点按类型分派JobType::Agent run_agent_job(...)JobType::Shell走 shell 路径超时SHELL_JOB_TIMEOUT_SECS 120秒JobType::Flow发布FlowScheduleTick事件失败后指数退避backoff_ms初始为provider_backoff_ms.max(200)每次sleep(backoff jitter)后backoff_ms min(backoff * 2, 30_000)上限 30 秒确定性策略违规不重试。最值得一提的是源码中针对重试无意义的永久用户状态做的一组**首次即停halt-on-first-occurrence**守卫全部限定在JobType::Agentlast_agent_error优先、last_output兜底匹配守卫函数识别的状态处理is_session_expired_failure后端 JWT 失效401Invalid token全局SessionExpired事件 凭据清理已由别处处理重试只会用掉所有退避次数再上报is_insufficient_credits_failureBYO 供应商余额不足402永久用户状态本地无杠杆可恢复is_budget_exhausted_failure托管后端预算耗尽USER_INSUFFICIENT_CREDITS400同上is_api_key_unset_failure供应商未配置 API key请求在凭据守卫处就被拦截永久配置状态is_local_provider_unreachable_failure本地 LLMOllama/LM Studio/llama.cpp不可达或no model loaded应用无法替用户启动本地推理服务这些守卫同时跳过report_error上报因为观测层的既有分类器已把这些视为预期用户状态继续上报只会制造噪音。源码注释引用了具体的线上问题编号TAURI-RUST-514 / -BMW / -HCK / -12K说明动机。失败时用户看到的通知文案由agent_error_to_user_message分类产生且有一个被测试锚定的合同该函数只返回静态static str常量永远不得把err的任何字段插值进输出错误体内可能含堆栈、带 query token 的 URL、部分响应体甚至用户输入。例如 API key 未设置的固定文案是 No API key is set for your AI provider. Add it in Connections → API keys → LLM, then re-run.。6.4 运行失败后的重调度作业失败后由reschedule_after_run重算next_run周期任务继续排期一次性任务则依赖delete_after_run清理record_run/record_last_run负责把本次运行写入运行历史与作业快照。七、投递层proactive / announce / none 三模式README 的 Delivery modes 一节是本模块最重要的语义约定投递逻辑在deliver_if_configuredscheduler 实现中实现proactiveagent 作业默认——发布DomainEvent::ProactiveMessageRequested { source: cron:{job_id}, message, job_name }。proactive 订阅者channels::proactive的ProactiveMessageSubscriber始终把消息推入应用内 web 流并在channels_config.active_channel已设置时镜像到该渠道。适合天然面是桌面 UI的作业晨报、应用推送通知。announce——显式指定渠道投递。要求channel与to都非空发布DomainEvent::CronDeliveryRequested { job_id, channel, target, output }只落在该渠道。当 cron 从非 web 渠道Telegram、Discord、Slack 等创建时agent 层应选此模式让提醒回到用户发起请求的地方。cron_add工具在创建时会把to与该渠道的allowed_users名单核对拒绝跨租户目标。none——静默输出只存在last_output中。投递前有两道过滤scheduler 实现 中的deliver_if_configured开头失败或空输出的运行不投递到聊天should_deliver_cron_output_to_chat success !empty否则一次瞬时网络错误会把 canned 的 Something went wrong 消息以没有用户消息托底的形式扔进对话失败必须进告警 tabcron_result_should_alert保证任何失败运行都进入/notifications与投递模式无关——一个none模式的 agent 作业若因缺 key 而永久失败用户仍能看见而成功但为空的运行则完全静默避免给显式静默的后台作业每个周期塞一条未读告警。bus.rs 的CronDeliverySubscriber负责把CronDeliveryRequested落到具体渠道按小写渠道名查channels_by_name命中则ch.send(SendMessage::new(output, target))未命中或发送失败时通过crate::core::observability::report_error上报failure标签分别为channel_missing/channel_send。这样调度器本身不需要构造任何渠道实例——渠道构造被完全隔离在 bus 层之外这正是模块说明里delivery layer that publishes events into the agent / channel pipelines的落点。非 web 入站的路由约定channels::runtime::dispatch为来自渠道的入站回合注入[Channel context]块指示模型默认用announce模式 当前渠道 回复目标。cron_add工具的 description 里也重申了这条规则When the current turn includes a[Channel context]block (e.g. Telegram, Discord, Slack), setdeliveryto{ mode: announce, channel: channel, to: reply target from the context block }so the reminder is delivered back to the same chat instead of the desktop. 这就是在 Telegram 里说提醒我喝水这类用例能路由回原聊天的路径。八、agent 工具面cron_add 的参数与校验schema 定义 之外的另一入口是 agent 工具。cron_addtools/add.rs的参数 schema 摘要{ required: [name, schedule], properties: { name: string建议始终提供, schedule: cron / at / every 三选一见第二节的 JSON 形式cron 可带 tz 与 active_hours, job_type: shell | agent, command: shell 类型使用, prompt: agent 类型使用, session_target: isolated | main, model: 可选模型, delivery: { mode: proactive | announce | none, channel: announce 必填, to: announce 必填, best_effort: boolean默认 true }, delete_after_run: boolean } }三个安全要点announce 模式的allowed_users校验validate_deliverymode ! announce直接放行announce 模式要求channel与to均非空然后用allowed_users_for_channel在 telegram / discord / slack / mattermost / matrix / irc / lark / dingtalk / qq 中查该渠道的allowed_users名单——to不在名单内即拒绝delivery target {to} on channel {channel} is not in allowed_users ...未知渠道走通用拒绝。注释写明动机This blocks an LLM (or RPC caller) from scheduling a cron whose output gets sent to an arbitrary chat id。权限级别permission_level()返回Execute——因为定时任务会持久化一条日后在主机上执行的命令或 agent 提示词渠道级权限上限与审批门approval gate都会介入外部副作用标记external_effect()返回 true确保即便回合来自入站渠道消息写入磁盘前ApprovalGate::intercept也会被调用注释引用了安全公告 GHSA-f46p-6vf9-64mm。其余工具cron_list/cron_update/cron_remove/cron_run/cron_runstools/ 下各文件与第五节 RPC 语义一一对应让 agent 能自助管理自己的定时任务。九、seed内置晨报作业与遗留清理seed.rs 在 onboarding 完成后被调用一次seed_proactive_agents职责有三去重dedup_named_jobs清理旧版本检查后插入非原子写法留下的同名重复作业best-effort失败仅告警清理遗留 welcome 作业早期构建种过一次性welcome作业delete_after_run Schedule::At若升级窗口内调度器没来得及触发残留条目会与现在由渲染层直接触发的欢迎消息双重投递故按稳定name字段扫描并删除另有prune_retired_jobs在每次核心启动时删除已下线功能TinyPlace autopilot遗留的agent_id tinyplace_agent行种入晨报作业morning_briefing调度Schedule::Cron { expr: 0 7 * * *, tz: None, active_hours: None }设备本地时间每天 7:00agent_id morning_briefingSessionTarget::Isolateddelete_after_run false投递为proactive不指定渠道由 channels 模块按用户活跃渠道决定去向。一个值得注意的决策晨报作业创建时enabled false且在单次插入中原子地以禁用状态创建。源码注释的理由是晨报是一次完整的 proactive agent 回合在用户从 Settings/Routines 显式启用cron.update_job → enabledtrue之前不应开始计费推理。整个 seed 流程是幂等的同名作业已存在则跳过。十、scheduler_gate主机状态门控补充机制cron 目录下的 scheduler_gate 模块 与调度器本体配套存在它回答现在允许后台 AI 工作运行吗这一问题。要点每 30 秒采样主机信号电源状态 AC/电池、近期全局 CPU 使用、部署模式 server/container支持OPENHUMAN_ON_AC_POWER/OPENHUMAN_BATTERY_CHARGE/OPENHUMAN_DEPLOYMENT环境变量覆盖计算Policy档位Aggressive服务器/常驻、Normal桌面有余量、Throttled忙碌或电池供电、Paused用户关闭、require_ac_power下电池供电、CPU 压力或已登出进程级单槽 LLM 信号量防止并发本地推理调用占满笔记本内存wait_for_capacity()在Throttled下睡眠throttled_backoff_ms、在Paused下每paused_poll_ms轮询档位一翻回即刻恢复set_signed_out/is_signed_out登出急停应用会话失效瞬间停掉一切后台 LLM 工作由凭据生命周期调用配置块为[scheduler_gate]modeAuto/AlwaysOn/Off、battery_floor、cpu_busy_threshold_pct、cpu_severe_pct、throttled_backoff_ms、paused_poll_ms、require_ac_power阈值越界会被防御性钳制防止一个写错的config.toml悄悄关闭或强制节流工作。该模块没有 RPC、没有 agent 工具、不发事件、无持久化进程内存单例被记忆树、embeddings、反思、本地推理等后台管线以current_policy()/wait_for_capacity()直接查询。对 cron 使用者而言它的实际意义是定时 agent 作业在电池供电或高负载的主机上可能被门控推迟这是local-first设备体验的一部分。十一、测试布局README 列出的测试在仓库中均可找到对应文件单元ops_tests.rs、scheduler_tests.rs另含分片的scheduler_tests_part_0*.rs、store_tests.rs、types_tests.rs、schedule_tests.rs、bus_tests.rs、seed_tests.rs、schemas_tests.rs以及 tools/ 下每个工具的*_tests.rs调度/解析/schema 覆盖大量内嵌在各文件的#[cfg(test)] mod tests块中投递校验announce 模式allowed_users检查位于cron::tools::add::tests错误分类的不泄漏错误内容合同由scheduler_tests中classifier_does_not_leak_error_content锚定集成层面另有 automation_scheduling_e2e.rs 等 e2e 测试覆盖定时/自动化场景。十二、实践要点小结把散落的机制收敛成一份使用清单调度不补跑调度器按 15 秒最低 5 秒轮询设备休眠或错过触发点不会事后补执行一次性at作业必须在未来时刻否则创建即被拒绝时区与活跃窗口cron 表达式按 IANA 时区缺省设备本地时区解释active_hours用本地时刻过滤跨午夜窗口受支持表达式支持 5/6/7 字段安全是默认值shell 命令在写入与更新时都过SecurityPolicy白名单agent 工具的cron_add走Execute权限级 审批门announce 投递被allowed_users限定无法把定时输出发往任意聊天失败可见、噪音受控任何失败都进告警 tab失败/空输出不进聊天五类永久错误首次失败即停、不再空转重试、不再重复上报用户文案全部为静态常量杜绝错误内容外泄静默与投递解耦none模式把输出留在last_output落库截断 16 KBproactive面向应用内 活跃渠道镜像announce精确落回发起渠道内置作业按需启用morning_briefing以禁用态种入用户显式开启后才开始消耗推理预算主机门控[scheduler_gate]决定电池/高负载场景下后台 LLM 工作的节流或暂停登出即全停。理解这套解析—存储—轮询—重试—分类—投递—告警的完整链路再对照 cron 模块说明 给出的公共面清单你可以在 OpenHuman 中既用 RPC/设置界面管理定时任务也能在需要时直接定位到对应源文件做二次开发例如新增投递模式、调整重试策略或扩展 seed 作业。【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考