Skip to content

Repository files navigation

NASDK

NASDK

Nyirusu Application Software Development Kit 是一套通用的长时资源有限任务调度与多应用间通讯框架。

NASDK被设计出来解决这样一类问题:一个任务要跑很长时间、中途要上报内容、可能被取消、还要和别的任务抢同一份资源,同时这些任务可能分散在多个独立进程里,彼此要能互相调用、互相订阅。

NASDK 将系统拆成两个相互独立的部分:

  • NACP + NACT:使用 RPC-style 消息协议和 CBOR 二进制承载,统一 TCP、Unix Socket 与 WebSocket 上的 App 间通信;
  • NACEB + NACAB:分别处理有过程流的 Event 和一次性请求响应 Ability。其中 NACEB 通过简单状态机占用资源类对单个 NACEB 实例内的独占资源进行统一调度。

NASDK统一了任务具体执行与通讯过程,不再区分能力上的Client/Server,并允许Web浏览器使用同一套标准的交互方式来实现事件处理和通讯。

NASDK 是可信多 App 环境下的轻量通信 Fabric,并承载有限资源工作流调度器的需求。

NASDK为什么很适合资源占用型任务调度

这是一个很现实的问题,本地LLM采样时需要占据几乎所有的GPU资源。对于文本采样时,我们当然可以在启动前给本任务加锁,直到之前的任务完成。

但是当任务变多、变复杂时,这一解决方法变得极其脆弱,你要在每个资源占用任务前给自己加锁,并且加锁类型也会变得复杂。此时你很可能需要一个队列来维持所有资源占用任务,并通过信号告知任务执行时机。

NASDK确实脱胎于Nyirusu Project,一个本地LLM Agentic Runtime。但是设计上,**NASDK 不是 AI SDK。**NASDK 不包含任何 LLM、模型、Token 或 ToolCall 语义;这些能力由构建在其上的 NAISDK 提供。

它没有 prompt、没有 model、没有 token、没有 tool calling,没有任何 AI 语义。设计上它确实很适合承载 LLM 应用(流式输出、可中断、GPU 独占、多轮工具调用都是它的原生场景)。

但我们认为,这都是应用层的事,所以 NASDK 可以脱离 Nyirusu 单独复用。

如果你再考虑NASDK的适合AI的高层包装,可以试试NAISDK

特性

你可以用NASDK轻易完成下列事件:

  • 让一个NApp发送请求到另一个NApp,并且能够监听另一个NApp中本任务的执行过程。
  • 把超大二进制直接塞到json里面,然后通过NApp接口发送到另一个NApp。
  • 统一前后端格式,能够在浏览器上直接运行NApp,并统一写法。
  • 直接将NApp.bus作为EventEmitter,能够在上面发送自定义消息,并且能够直接在另一个NApp上远程订阅。
  • 使用NACEB来托管资源占用型任务,比如llama.cpp的textCompletion,并且能在另一个NApp发起请求远程调用。
  • 使用NACEB来作为AI应用中的ToolCall部分,能够直接暴露能力与简介,并提供了海量观测事件和Hook用于监听和介入。
  • 在一个任务执行期间能够远程取消一个任务的执行,在允许的情况下甚至能够远程重试子任务。
  • 一个任务执行期间可以触发构建子任务SubEvent,并且能够选择并发还是阻塞等待结果。
  • NACEB被设计为流水线方式,你可以轻松编写对应的PipelineHandler来完成复杂的任务调用。
  • NApp完全可以运行在用户网页中,并且可以直接把事件和能力放在用户浏览器上处理。除了不能自行启动Server和必须用WebSocket来连接到另一个NApp,和普通NApp没有差异。

NASDK默认导出五个构件,包括NApp/NACP/NACT/NACEB/NACAB

您很有可能会被这些名字吓住,但是不必担心,他们的语义非常明确:

NAC其实是Nyirusu Application Control的缩写,表示NApp控制相关。

Nyirusu 是什么意思请不要在这个仓库里询问!😡

NACP/NACT,P表示Protocol,表示传输协议。T表示Transparent,你也可以理解成Transport,表示物理传输方式。

NACAB/NACEB,表示AbilityBus和EventBus,是实际上能力与事件请求的处理Processor。

NASDK将所有事件归类为七大族(un)register/(un)subscribe/notify/request/response,当NApp接收到对应消息后,会区分EventAbility,并将对应请求发送到对应的Processor。

其中,发送Event的请求方还可以直接监听到对应执行方中的过程流,具体调用方法可以参见订阅与通知

两种调用,两种处理器

NASDK 把一次远程调用分成两类,区别只有一处,要不要过程流

Event Ability
形态 多步骤、有生命周期、可暂停可取消 一次调用、无状态、可能被大量并发
回包 1 条 response + 0~N 条过程 notify 1 条 response
资源分配 会互相争抢 不会
默认处理器 NACEB NACAB

选哪个由你决定。协议层对两者只有一处实质差异(Event 会自动订阅过程流),其余完全同构。

资源调度

NACEB 的通过区分BlockedTaskAsyncTask,并在前者通过busyKeys隔离资源占用来实现调度。

每一次状态转移都可观测(EventBus)、可介入(Hook)、可否决(Veto)。

构建一个NApp应用

安装

需要 Node.js 20+。

npm install @chenyfan/nasdk

开始编写

import NApp, { NACEB, NACAB } from '@chenyfan/nasdk'
import { TaskHandler, PipelineHandler } from '@chenyfan/nasdk/NACEB'
import { AbilityHandler } from '@chenyfan/nasdk/NACAB'


// ── Task:“如何具体执行一个任务”──
class TranslateCompletion extends TaskHandler {
  name = 'translateCompletion'
  description = '使用LLM模型实现文本翻译'
  busyKeys = ['gpu']
  async execute() {
    let out = ''
    for await (const delta of llm.textCompletionStream(`翻译成中文:\n${this.input}`)) {
      out += delta
      this.processingResultReport({ stage: 'translating', delta })
    }
    return out                                        
  }
}

class SummaryCompletion extends TaskHandler {
  name = 'summaryCompletion'
  description = '使用LLM模型实现文本总结'
  busyKeys = ['gpu']
  async execute() {
    return await llm.textCompletion(`总结以下内容:\n${this.input.join('\n')}`)
  }
}

// ── Pipeline:只决定“下一步跑哪个 task、输入应该是什么”──
class TranslationPipe extends PipelineHandler {
  name = 'Trans'
  description = '翻译流水线,并在翻译结束后完成对所有翻译内容的总结'
  next(lastResult) {
    if (lastResult === undefined) {                    // 首步,从 event 的 payload 取输入
      this.state.done = []
      this.state.todo = [...this.event.payload.paragraphs]
      return { task: 'translateCompletion', input: this.state.todo.shift() }
    }
    if (this.state.summarized) return { task: '$terminal', input: { paragraphs: this.state.done, summary: lastResult } }
    this.state.done.push(lastResult) 
    if (this.state.todo.length) return { task: 'translateCompletion', input: this.state.todo.shift() }
    this.state.summarized = true
    return { task: 'summaryCompletion', input: this.state.done }
  }
}

const llm = {       //Demo Function
  async *textCompletionStream(text) {
    yield `[translated] ${text}`
  },
  async textCompletion(text) {
    return `[summary] ${text}`
  },
}

class Collatz extends AbilityHandler {
  name = 'math.collatz'
  description = '冰雹函数计算'
  async execute() {
    let n = this.input.n, steps = 0
    while (n !== 1) { n = n % 2 ? n * 3 + 1 : n / 2; steps++ }
    return { steps }
  }
}

const naceb = new NACEB({
  pipelineHandlers: [new TranslationPipe()],
  taskHandlers:     [new TranslateCompletion(), new SummaryCompletion()],
  eventAlias: [{ eventName: 'translate', pipelineName: 'Trans', description: '翻译并总结' }],
})
const nacab = new NACAB({ handlers: [new Collatz()] })

const app = new NApp({
  id: 'ai-core',
  server: [
    { type: 'unix', opt: { socketPath: '/tmp/core.sock' } },        
    { type: 'ws',   opt: { ip: '127.0.0.1', port: 8080, path: '/ws' } },
  ],
})

app.bindProcessor('event',   naceb.nacpAdaptor)
app.bindProcessor('ability', nacab.nacpAdaptor)

await app.start()                                     

调用

你可以额外启动一个NApp,通过unix Socket来与之前的Napp交互。

const app = new NApp({ id: 'client-app' })
await app.start()
await app.connect('ai-core', { type: 'unix', opt: { socketPath: '/tmp/core.sock' } })


const res = await app.request('ai-core', {
  kind: 'event', 
  target: 'translate', 
  payload: { paragraphs: ['Hello', 'World'] },
  onProcess: (chunk) => process.stdout.write(chunk.delta),   //可以远程监听执行过程中的内容
})
console.log(res.payload.summary)

const { payload } = await app.request('ai-core', { kind: 'ability', target: 'math.collatz', payload: { n: 27 } })
console.log(payload.steps)   // => 111

如果对端是存在Web浏览器中的,则只能通过WebSocket来和Server通讯:

const app = new NApp({ id: 'client-web' })                       // 注意,Web NApp不能填写server,是纯client的。但这并不意味着Web NApp不能有能力和事件处理,你完全可以让Web NApp提供能力,让服务器来调用。
await app.start()
await app.connect('ai-core', { type: 'ws', opt: { ip: '127.0.0.1', port: 8080, path: '/ws' } })
//调用方式没有区别
const res = await app.request('ai-core', {
  kind: 'event', 
  target: 'translate', 
  payload: { paragraphs: ['Hello', 'World'] },
  onProcess: (chunk) => { document.querySelector('#out').textContent += chunk.delta },
})

文档

  • NApp —— NASDK Facade,讲述了装配、生命周期和NApp完整 API
  • NACP —— NASDK 通讯协议,讲述了具体消息格式、配对语义、路由与 Gateway
  • NACT —— NASDK 物理通讯承载协议,讲述分片格式、心跳和网络通讯。浏览器可用,但只能作 ws 客户端、不能建 server。
  • NACEB —— NASDK 默认有状态的事件处理机,讲述资源竞争、Hook字段、Veto 与 SubEvent。
  • NACAB —— NASDK 默认无状态的能力处理机,讲述具体字段和实现。
  • EventBus —— NASDK 自带的事件总线,提供本地通配符订阅与异步订阅。

注意

  • NASDK完全没有设计鉴权系统,我们在设计时就认为,所有连接进来的 App 都可信。因此,请不要直接把通讯入口挂载在公网上!
  • NASDK/NACP从来没有设计为持久化消息队列形式,虽然NASDK提供了有限的消息队列缓存和中继支持,但不保证断线重连能否有效。
  • NACEB暂时没有设计为分布式协调,他不能自动协调两个NApp的资源占用。这一部分问题将在接下来的版本中解决。
  • NACAB和NACEB的AsyncTask被设计为绝对能够并发启动,没有设置上限。

License

MIT License

About

NASDK,a general-purpose framework for scheduling long-duration, resource-constrained tasks and for communication between multiple applications.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages