跳至主要內容

Agent(智能体)

起凡大约 3 分钟

Agent(智能体)

agent实际上也是一种Function call但是它内部还包含了一些Function call也就是tool。在Function Call中可以编写数据库查询,或者调用其他接口。也就是AI的记忆部分了。

前端实现

在前端的参数面板增加一个启用agent的按钮,点击后后端会把所有的agent加入function call中。

agent开关按钮

<el-form-item label="agent(智能体)">
    <el-switch v-model="options.enableAgent"></el-switch>
</el-form-item>

新增enableAgent记录是否启用agent

const options = ref<AiMessageParams>({
  enableVectorStore: false,
  enableAgent: false
})

后端实现

@Agent注解

在类上添加注解@Agent标识当前的类是一个agent。被@Agent标记的类意味着也被@Component标记了,也就是说它会注册为一个Bean

@Component
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Agent {
}

可以从ApplicationContext中获取被@Agent标记的Bean

// 获取带有Agent注解的bean
Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(Agent.class);

抽象Agent

有一些比较通用的方法可以抽取到抽象父类里面

  1. 要实现Function接口
  2. 都要创建一个ChatClient
  3. 获取内嵌的Function列表
@Slf4j
public abstract class AbstractAgent<Req, Resp> implements Function<Req, Resp> {
    private final ChatClient client;

    /**
     * 构建ChatClient方便子类使用
     *
     * @param chatModel 聊天模型
     */
    public AbstractAgent(ChatModel chatModel) {
        this.client = ChatClient
                .builder(chatModel)
                .defaultFunctions(getFunctions())
                .build();

    }

    public ChatClient getChatClient() {
        return client;
    }

    /**
     * 获取内嵌的Function Call也就是Agent的Tools
     *
     * @return Function Call名称列表
     */
    public String[] getFunctions() {
        List<Class<?>> classList = Arrays.stream(this.getClass().getClasses()).filter(aClass -> aClass.isAnnotationPresent(Description.class)).toList();
        String[] names = new String[classList.size()];
        classList.stream().map(aClass -> StringUtils.uncapitalize(this.getClass().getSimpleName()) + "." + aClass.getSimpleName()).toList().toArray(names);
        return names;
    }
}

例子 电脑助手Agent

  1. 首先需要继承AbstractAgent,实现Function接口要填写两个泛型ReqResp, 也就是当前Agent的入参类型和返类型。
    下面这个例子的入参是ComputerAssistant.Request,返回类型是String
  2. 实现apply方法,调用父类的getChatClient它会自动将当前Agent中的Function加入到Function Call中,也就是将DirectoryReaderCpuAnalyzer自动添加。参考父类的构造函数中的.defaultFunctions(getFunctions())
  3. 使用@Agent标识,这样才会注册到容器中,并且被启用。
  4. 使用@Description描述Agent的作用,这样可以在多个Agent中根据意图选择相应的Agent
@Agent
@Description("提供关于当前主机的cpu,文件,文件夹相关问题的有用回答")
public class ComputerAssistant extends AbstractAgent<ComputerAssistant.Request, String> {


    protected ComputerAssistant(ChatModel chatModel) {
        super(chatModel);
    }

    @Override
    public String apply(Request request) {
        return getChatClient()
                .prompt()
                .user(request.query())
                .call()
                .content();
    }

    public record Request(
            @JsonProperty(required = true) @JsonPropertyDescription(value = "用户原始的提问") String query) {
    }

    @Component
    @Description("读取用户给定的文件夹,列出文件夹下的所有文件")
    public static class DirectoryReader implements Function<DirectoryReader.Request, String> {
        @Override
        public String apply(Request request) {
            File f = new File(request.path);
            List<String> out = new ArrayList<>();
            if (f.exists()) {
                String[] list = f.list();
                if (list != null) {
                    out = Arrays.asList(list);
                }
            }
            return String.join(",", out);
        }

        public record Request(
                @JsonProperty(required = true) @JsonPropertyDescription("本机文件夹的绝对路径") String path
        ) {
        }
    }

    @Component
    @Description("读取CPU的数量")
    public static class CpuAnalyzer implements Function<CpuAnalyzer.Request, Integer> {
        @Override
        public Integer apply(Request request) {
            return Runtime.getRuntime().availableProcessors();
        }

        public record Request() {
        }
    }
}

扫描Agent并启用

当用户传入提问之后,大模型会从已有的Agent(被@Agent标注的类)中选择一个执行,如果没有匹配到大模型会自己回答。

    @PostMapping(value = "chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> chat(@RequestBody AiMessageWrapper input) {
        String[] functionBeanNames = new String[0];
        // 如果启用Agent则获取Agent的bean
        if (input.getParams().getEnableAgent()) {
            // 获取带有Agent注解的bean
            Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(Agent.class);
            functionBeanNames = new String[beansWithAnnotation.keySet().size()];
            functionBeanNames = beansWithAnnotation.keySet().toArray(functionBeanNames);
        }
        return ChatClient.create(chatModel).prompt()
                .user(promptUserSpec -> toPrompt(promptUserSpec, input.getMessage()))
                // agent列表
                .functions(functionBeanNames)
                // 忽略...
                .stream()
                .chatResponse()
                .map(chatResponse -> ServerSentEvent.builder(toJson(chatResponse))
                        // 和前端监听的事件相对应
                        .event("message")
                        .build());
    }