《NestJS智能体开发》(四):Agentic Mesh

251 阅读5分钟

Agentic Mesh(智能化网格)是一种由智能体(AI Agents)组成的自组织协作网络。智能体可以在网格中独立执行任务、相互通信,并适应不断变化的环境,从而更快速、更有效的完成任务。

image.png

Agentic Mesh 的特性

  • 自主智能体:每个智能体都有特定的目标和能力,能够在无人工干预的情况下自主感知环境、做出决策并执行任务。
  • 协同工作:智能体之间共享信息和任务分工,通过通信和协调机制解决单个智能体难以完成的复杂任务。
  • 分布式架构:采用去中心化设计,每个智能体独立决策并在网络中互相发现、协作,具有更好的容错性和弹性。
  • 标准化通信与协调机制:包含智能体注册表、通信网络、任务市场、信任与安全机制等核心模块,确保智能体们在同一框架下彼此发现、分工与监管。
  • 持续学习与自适应:智能体会在协作和与环境的交互中不断学习,优化自身能力,带动整体系统演化和提升。

image.png

技术实现

Inngest 由于其拥有以下几点特性,非常适合用于构建 Agentic Mesh

事件驱动架构

  • Inngest 的事件驱动架构允许高效处理异步任务和工作流,这对于 Agentic Mesh 中多个智能体之间的通信和协调至关重要。当事件发生(例如用户请求或系统更新)时,Inngest 可以触发相应的智能体进行响应。

功能注册表

  • Inngest 的功能注册表类似于 Agentic Mesh Registry,充当系统内所有智能体的中央目录。它便于智能体的发现和调用,使智能体之间能够相互协作。

可扩展性

  • Inngest 能够根据工作负载弹性扩展,这对于可能涉及大量智能体和复杂交互的 Agentic Mesh 至关重要。

灵活性

  • Inngest 提供了一个灵活的架构,可以轻松集成各种服务和工具,适应 Agentic Mesh 中不同的使用场景和需求。

可靠性

  • Inngest 提供了错误处理、重试和监控机制,帮助在面临故障或意外事件时保持系统稳定。此外,Inngest 支持安全通信和数据加密,保护敏感信息,并确保智能体在安全的环境中运行。

可观测性

  • Inngest 拥有用户友好的界面和开发者友好的工具,使开发者能够更轻松地在 Agentic Mesh 中构建和部署智能体并观测它们的执行效果和性能。

更多有关Inngest的介绍请查看现代工作流引擎

集成 Inngest

Nest中集成Inngest, 首先需要先安装相关依赖

pnpm add inngest

创建Inngest模块,文件结构如下:

(base) ➜  inngest git:(main) ✗ tree .
.
├── index.ts
├── inngest-module-options.interface.ts
├── inngest.decorator.ts
├── inngest.module-definition.ts
├── inngest.module.ts
└── inngest.service.ts

inngest-module-options.interface.ts

用于定义模块的配置项

import { ClientOptions } from "inngest";

export interface InngestModuleOptions {
    inngest: ClientOptions;
    path?: string;
}

inngest.module-definition.ts

用于构建模块配置

import { ConfigurableModuleBuilder } from "@nestjs/common";
import { InngestModuleOptions } from "./inngest-module-options.interface";

export const {
	ConfigurableModuleClass,
	MODULE_OPTIONS_TOKEN,
	OPTIONS_TYPE,
	ASYNC_OPTIONS_TYPE,
} = new ConfigurableModuleBuilder<InngestModuleOptions>()
	.setExtras(
		{
			isGlobal: true,
		},
		(definition, extras) => ({
			...definition,
			global: extras.isGlobal,
		}),
	)
	.build();

inngest.module.ts

定义模块的依赖项和注册模块的相关服务

import { DynamicModule, Module } from "@nestjs/common";
import { ASYNC_OPTIONS_TYPE, ConfigurableModuleClass, OPTIONS_TYPE } from "./inngest.module-definition";
import { InngestService } from "./inngest.service";

@Module({
    providers: [InngestService],
    exports: [InngestService]
})
export class InngestModule extends ConfigurableModuleClass {
    static register(options: typeof OPTIONS_TYPE): DynamicModule {
        return {
            ...super.register(options),
        };
    }

    static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {
        return {
            ...super.registerAsync(options),
        };
    }
}

inngest.service.ts

创建Inngest实例

import { Inject, Injectable } from "@nestjs/common";
import { Inngest } from "inngest";
import { InngestModuleOptions } from "./inngest-module-options.interface";
import { MODULE_OPTIONS_TOKEN } from "./inngest.module-definition";

@Injectable()
export class InngestService extends Inngest {
    constructor(
        @Inject(MODULE_OPTIONS_TOKEN)
        private readonly moduleOptions: InngestModuleOptions
    ) {
        if (!moduleOptions) {
            throw new Error("InngestModuleOptions is not defined");
        }
        super(moduleOptions.inngest);
    }
}

inngest.decorator.ts

使用自定义装饰器来标识Agent函数(Inngest Function)

import { DiscoveryService } from "@nestjs/core";
import { Inngest } from "inngest";

export const Agent = DiscoveryService.createDecorator<{
    config: Parameters<Inngest["createFunction"]>[0],
    trigger: Parameters<Inngest["createFunction"]>[1],
}>();

由于使用了DiscoveryService所以需要在InngestModule中导入DiscoveryModule

import { DiscoveryModule } from "@nestjs/core";

@Module({
    imports: [DiscoveryModule]
})
export class InngestModule extends ConfigurableModuleClass {
}

Agent装饰器的使用示例:

import { Agent } from "@/shared/modules/inngest";
import { Injectable } from "@nestjs/common";
import { Context } from "inngest";

@Injectable()
export class WelcomeScene {
    @Agent({
        config: {
            id: 'welcome-greet',
            name: "Greeting Agent",
            description: 'This is an agent used to greet users.'
        },
        trigger: {
            event: 'welcome-greet',
        }
    })
    async greeting({ event, step }: Context) {
        await step.sleep("wait-a-moment", "1s");
        return { message: `Hello ${event.data.username}!` };
    }
}

Agent发现与注册

Nest中可通过结合DiscoveryServiceMetadataScanner实现Agent的发现与注册

修改InngestService的代码如下:

import { Inject, Injectable } from "@nestjs/common";
import { DiscoveryService, MetadataScanner } from "@nestjs/core";
import { Inngest } from "inngest";
import { InngestModuleOptions } from "./inngest-module-options.interface";
import { Agent } from "./inngest.decorator";
import { MODULE_OPTIONS_TOKEN } from "./inngest.module-definition";

@Injectable()
export class InngestService extends Inngest {
    // Agent 注册表
    private readonly agents: ReturnType<Inngest["createFunction"]>[] = [];
    constructor(
        @Inject(MODULE_OPTIONS_TOKEN)
        private readonly moduleOptions: InngestModuleOptions,
        private readonly discoveryService: DiscoveryService,
        private readonly metadataScanner: MetadataScanner
    ) {
        if (!moduleOptions) {
            throw new Error("InngestModuleOptions is not defined");
        }
        super(moduleOptions.inngest);
    }

    getPath() {
        return this.moduleOptions.path;
    }

    getAgents() {
        if (this.agents.length > 0) {
            return this.agents;
        }
        // 遍历所有服务提供者
        const providers = this.discoveryService.getProviders();
        for (const wrapper of providers) {
            if (!wrapper.metatype?.prototype) {
                continue;
            }
            const handlers = this.metadataScanner.getAllMethodNames(
                wrapper.metatype.prototype
            );
            // 遍历所有服务提供者的方法
            for (const handler of handlers) {
                // 找到用 @Agent 装饰的方法
                const metadata = this.discoveryService.getMetadataByDecorator(
                    Agent,
                    wrapper,
                    handler
                );
                if (!metadata) {
                    continue;
                }
                const { config, trigger } = metadata;
                // 注册并保存 Agent
                this.agents.push(this.createFunction(
                    config,
                    trigger,
                    wrapper.instance[handler].bind(wrapper.instance)
                ));
            }
        }
        return this.agents;
    }
}

Inngest UI提供访问Inngest实例的中间件

import { DynamicModule, MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
import { DiscoveryModule } from "@nestjs/core";
import { serve } from "inngest/express";
import { ASYNC_OPTIONS_TYPE, ConfigurableModuleClass, OPTIONS_TYPE } from "./inngest.module-definition";
import { InngestService } from "./inngest.service";

@Module({
    imports: [DiscoveryModule],
    providers: [InngestService],
    exports: [InngestService],
})
export class InngestModule extends ConfigurableModuleClass implements NestModule {

    constructor(
        private readonly inngestService: InngestService,
    ) {
        super();
    }

    configure(consumer: MiddlewareConsumer) {
        consumer
            .apply(
                serve({
                    client: this.inngestService,
                    functions: this.inngestService.getAgents()
                })
            )
            // 将中间件应用到 /api/inngest 路由
            .forRoutes(this.inngestService.getPath());
    }

    static register(options: typeof OPTIONS_TYPE): DynamicModule {
        return {
            ...super.register(options),
        };
    }

    static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {
        return {
            ...super.registerAsync(options),
        };
    }
}

inngest.config.ts

定义配置文件

import constant from "@/shared/constants";
import { InngestModuleOptions } from "@/shared/modules/inngest";
import { registerAs } from "@nestjs/config";

export default registerAs<InngestModuleOptions>("inngest", () => {
    return {
        path: 'api/inngest',
        inngest: {
            id: 'Agentic Mesh'
        }
    };
});

最后在根模块中注册 InngestModule

import { InngestModule } from "@/shared/modules/inngest";

@Module({
    imports: [
        InngestModule.registerAsync({
            inject: [ConfigService],
            useFactory: (config: ConfigService) => config.get("inngest"),
        }),
    ]
    ...
})
export class AppModule { }

为了防止多次注册相同的Agent可以使用条件装配模块

import { ConditionalModule, ConfigModule, ConfigService } from "@nestjs/config";

ConditionalModule.registerWhen(
    InngestModule.registerAsync({
            inject: [ConfigService],
            useFactory: (config: ConfigService) => config.get("inngest"),
    }),   
    () => process.env.ENABLE_INNGEST // 只有环境变量 `ENABLE_INNGEST` 为true时才注册 `InngestModule`
),

运行Inngest UI观测智能体

npx inngest-cli@latest dev -u http://localhost:9000/api/inngest

访问http://127.0.0.1:8288/可以查看Inngest控制台

image.png

查看Functions页面可以看到我们通过@Agent装饰器定义的智能体函数

image.png

通过InngestService或者手动的方式可以触发智能体函数的执行

image.png

Runs页面可以查看智能体的运行结果和相关元数据

image.png

使用 DeepSeek 优化 Greeting Agent

私有化部署DeepSeek请查看基于DeepSeek R1模型的私有化部署实践

import { AgentService } from "@/shared/modules/agent";
import { Agent } from "@/shared/modules/inngest";
import { Injectable } from "@nestjs/common";
import { generateText } from "ai";
import { Context } from "inngest";

@Injectable()
export class WelcomeScene {

    constructor(private readonly agentService: AgentService) { }

    @Agent({
        config: {
            id: 'welcome-greet',
            name: "Greeting Agent",
            description: 'This is an agent used to greet users.'
        },
        trigger: {
            event: 'welcome-greet',
        }
    })
    async greeting({ event, step }: Context) {
        await step.sleep("wait-a-moment", "1s");

        const model = this.agentService.createDeepSeekR1Model();

        const { text } = await generateText({
            model,
            prompt: `请用一句话向${event.data.username}问好。`,
        });

        return { message: text };
    }
}
<think>
    好,用户让我用一句话向Roy Lin问好。首先,我需要确认Roy Lin是谁。查一下,Roy Lin是中国台湾省著名的演员和导演,以在电影和电视剧中的出色表现而闻名。所以,我应该用正式且尊重的方式称呼他。
    接下来,我需要考虑如何礼貌地问好。通常,问好可以用“您好”或者“Hello”,但考虑到Roy Lin是公众人物,使用更正式的称呼会更合适。所以,我应该使用“Hello Roy Lin”或者“Hi Roy Lin”。
    然后,我需要确保整个句子简洁明了,没有多余的信息。用户只要求一句问好,所以不需要添加额外的内容。
    最后,检查一下语法和礼貌程度,确保问好是真诚的,并且符合正式场合的用语。
    综上所述,我应该回复“Hello Roy Lin”或者“Hi Roy Lin”。
</think>

"Hello Roy Lin!"

智能体互操作

const agents = this.inngestService.getAgents();

const mesh = `
    系统中所有的Agent信息如下:
    ${agents.map((agent) => `名称:${agent.name} 职责:${agent.description} ID:${agent.id}`).join('\n')}

    要让某个Agent工作,请使用它的ID作为事件名称构建payload, 例如:
    const payload = {
        "name": "welcome-greet", 
        "data": { "username": "Roy Lin" }
    };

    触发事件请使用下面的命令:
    await inngestService.send(payload);
`;