整合spring ai alibaba + ollama 实现一个可以执行python代码、读、写txt文档功能的Agent

整合spring ai alibaba + ollama 实现一个可以执行python代码、读、写txt文档功能的Agent

一、环境

ollama 软件

jdk17

docker 安装 redis/redis-stack:7.2.0-v17 (可选,后面实现RAG功能 可以选择这个)

二、maven 依赖

2.1 spring-ai-alibaba-agent-framework -- spring-ai-alibaba的agent依赖包

2.2 spring-ai-starter-model-ollama -- ollama的agent依赖包

2.3 spring-ai-starter-vector-store-redis -- redis向量存储依赖 (可选,后面实现RAG功能 可以选这个)

2.4 spring-ai-alibaba-studio -- spring-ai-alibaba提供聊天UI

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- <parent>
         <groupId>com.example</groupId>
         <artifactId>oak-spring-ai-alibaba-demos</artifactId>
         <version>1.0-SNAPSHOT</version>
     </parent>-->

    <groupId>com.example</groupId>
    <artifactId>oak-spring-ai-alibaba-ollama</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <graalvm.polyglot.version>24.2.1</graalvm.polyglot.version>

        <spring.ai.version>1.1.0-M4</spring.ai.version>
        <spring.boot.version>3.4.3</spring.boot.version>
        <spring.ai.alibaba.version>1.1.0.0-M5</spring.ai.alibaba.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring.ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>


            <dependency>
                <groupId>com.alibaba.cloud.ai</groupId>
                <artifactId>spring-ai-alibaba-bom</artifactId>
                <version>${spring.ai.alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-agent-framework</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-ollama</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-vector-store-redis</artifactId>
        </dependency>

      

        <!-- GraalVM Polyglot for Python execution -->
        <dependency>
            <groupId>org.graalvm.polyglot</groupId>
            <artifactId>polyglot</artifactId>
            <version>${graalvm.polyglot.version}</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.graalvm.polyglot</groupId>
            <artifactId>python-community</artifactId>
            <version>${graalvm.polyglot.version}</version>
            <type>pom</type>
            <optional>true</optional>
        </dependency>


        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-studio</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.alibaba.cloud.ai</groupId>
                    <artifactId>spring-ai-alibaba-dashscope</artifactId>
                </exclusion>

            </exclusions>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>3.4.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version> <!-- 请根据需要选择合适的插件版本 -->
                <configuration>
                    <source>17</source> <!-- 指定源码版本,例如 JDK 17 -->
                    <target>17</target> <!-- 指定目标字节码版本 -->
                </configuration>
            </plugin>

        </plugins>
    </build>

    <repositories>

        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>


        <repository>
            <id>spring-snapshots</id>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>


        <repository>
            <id>central-portal-snapshots</id>
            <name>Central Portal Snapshots</name>
            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>

    </repositories>

</project>

三、application.yml 配置

yaml 复制代码
spring:
  ai:
    ollama:
      # 本地ollama 地址
      base-url: http://localhost:11434
      # 聊天模型
      chat:
          model: llama3.2
      # 向量模型
      embedding:
        model: llama3.2
    # 向量数据库 使用 redis-stack
    vectorstore:
      type: redis
      redis:
        initialize-schema: true
        index-name: default-index
  # redis 配置
  data:
    redis:
      host: 127.0.0.1
      port: 6379

四、主要Java代码,主要配置 ReactAgent

4.1、主要配置 ReactAgent

java 复制代码
package com.example.config;

import com.alibaba.cloud.ai.agent.studio.loader.AgentLoader;
import com.alibaba.cloud.ai.graph.agent.AgentTool;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.extension.tools.filesystem.ReadFileTool;
import com.alibaba.cloud.ai.graph.agent.extension.tools.filesystem.WriteFileTool;
import com.alibaba.cloud.ai.graph.agent.tools.ShellTool;
import com.example.hooks.CustomAgentHook;
import com.example.hooks.CustomModelHook;
import com.example.interceptors.LoggingModelInterceptor;
import com.example.interceptors.ToolMonitoringInterceptor;
import com.example.tools.DocumentSearchTool;
import com.example.tools.PythonTool;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import java.io.File;

@Configuration
public class ReactAgentConfig {

    private static final String INSTRUCTION = """
            You are a helpful assistant named research_agent.
            You have access to tools that can help you execute shell commands, run Python code, write text files and view text files.
            Use these tools to assist users with their tasks.
            """;

    @Bean
    public AgentLoader agentLoader(ReactAgent reactAgent) {
        return new AgentStaticLoader(reactAgent);
    }

    @Bean("research_agent")
    @Primary
    public ReactAgent researchAgent(ChatModel chatModel,
                                    VectorStore vectorStore,
                                    @Qualifier("executeShellCommand") ToolCallback executeShellCommand,
                                    @Qualifier("executePythonCode") ToolCallback executePythonCode,
                                    @Qualifier("viewTextFile") ToolCallback viewTextFile,
                                    @Qualifier("writeFileTool") ToolCallback writeFileTool
    ) {
        DocumentSearchTool searchTool = new DocumentSearchTool(vectorStore);
        // 创建工具回调
        ToolCallback searchCallback = FunctionToolCallback.builder(
                        "search_documents",
                        searchTool
                )
                .description("搜索文档以查找相关信息")
                .inputType(DocumentSearchTool.Request.class)
//                .inputType(String.class)
                .build();

        return ReactAgent.
                builder()
                .name("research_agent")
                .model(chatModel)
                /*.instruction(
                        "你是一个智能助手。当需要查找信息时,使用search_documents工具。" +
                                "基于检索到的信息回答用户的问题,并引用相关片段。"
                )*/
                .instruction(
                        INSTRUCTION
                )
                .tools(
//                        searchCallback ,
                        executeShellCommand,
                        executePythonCode,
                        viewTextFile,
                        writeFileTool
                )
                .interceptors(
                        new LoggingModelInterceptor(vectorStore),
                        new ToolMonitoringInterceptor()
                )

                .hooks(new CustomAgentHook(), new CustomModelHook())
//                .enableLogging(true)
                .build();
    }


    // Tool: execute_shell_command
    @Bean
    public ToolCallback executeShellCommand() {
        // Use ShellTool with a temporary workspace directory
        String workspaceRoot = System.getProperty("java.io.tmpdir") + File.separator + "agent-workspace";
        return ShellTool.builder(workspaceRoot)
                .withName("execute_shell_command")
                .withDescription("Execute a shell command inside a persistent session. Before running a command, " +
                        "confirm the working directory is correct (e.g., inspect with `ls` or `pwd`) and ensure " +
                        "any parent directories exist. Prefer absolute paths and quote paths containing spaces, " +
                        "such as `cd "/path/with spaces"`. Chain multiple commands with `&&` or `;` instead of " +
                        "embedding newlines. Avoid unnecessary `cd` usage unless explicitly required so the " +
                        "session remains stable. Outputs may be truncated when they become very large, and long " +
                        "running commands will be terminated once their configured timeout elapses.")
                .build();
    }

    // Tool: execute_python_code
    @Bean
    public ToolCallback executePythonCode() {
        return FunctionToolCallback.builder("execute_python_code", new PythonTool())
                .description(PythonTool.DESCRIPTION)
                .inputType(PythonTool.PythonRequest.class)
                .build();
    }

    // Tool: view_text_file
    @Bean
    public ToolCallback viewTextFile() {
        // Create a custom wrapper to match the original tool name
        ReadFileTool readFileTool = new ReadFileTool();
        return FunctionToolCallback.builder("view_text_file", readFileTool)
                .description("View the contents of a text file. The file_path parameter must be an absolute path. " +
                        "You can specify offset and limit to read specific portions of the file. " +
                        "By default, reads up to 500 lines starting from the beginning of the file.")
                .inputType(ReadFileTool.ReadFileRequest.class)
                .build();
    }

    // Tool: write_file_tool
    @Bean
    public ToolCallback writeFileTool() {
        // Create a custom wrapper to match the original tool name
        WriteFileTool writeFileTool = new WriteFileTool();
        return FunctionToolCallback.builder("write_file_tool", writeFileTool)
                .description(
                        """
                                    Writes to a new file in the filesystem.
                                    Usage:
                                    - The file_path parameter must be an absolute path, not a relative path
                                    - The content parameter must be a string
                                    - The write_file tool will create a new file.
                                    - When writing to a file, the content will completely replace the existing content.
                                """
                )
                .inputType(WriteFileTool.WriteFileRequest.class)
                .build();
    }

}

4.2 编写一个Python tool 类 (读写文件工具类:ReadFileTool、WriteFileTool 由spring-ai-alibaba-agent-framework的框架自带)

java 复制代码
package com.example.tools;



import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;

import java.util.function.BiFunction;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Tool for executing Python code using GraalVM polyglot.
 *
 * This tool allows the agent to execute Python code snippets and get results.
 * It uses GraalVM's polyglot API to run Python code in a sandboxed environment.
 */
public class PythonTool implements BiFunction<PythonTool.PythonRequest, ToolContext, String> {

    public static final String DESCRIPTION = """
          Executes Python code and returns the result.
          
          Usage:
          - The code parameter must be valid Python code
          - The tool will execute the code and return the output
          - If the code produces a result, it will be returned as a string
          - Errors will be caught and returned as error messages
          - The execution is sandboxed for security
          
          Examples:
          - Simple calculation: code = "2 + 2" returns "4"
          - String operations: code = "'Hello, ' + 'World'" returns "Hello, World"
          - List operations: code = "[1, 2, 3][0]" returns "1"
          """;
    private static final Logger log = LoggerFactory.getLogger(PythonTool.class);
    private final Engine engine;

    public PythonTool() {
        // Create a shared engine for better performance
        this.engine = Engine.newBuilder()
                .option("engine.WarnInterpreterOnly", "false")
                .build();
    }

    /**
     * Create a ToolCallback for the Python tool.
     */
    public static ToolCallback createPythonToolCallback(String description) {
        return FunctionToolCallback.builder("python", new PythonTool())
                .description(description)
                .inputType(PythonRequest.class)
                .build();
    }

    @Override
    public String apply(PythonRequest request, ToolContext toolContext) {
        if (request.code == null || request.code.trim().isEmpty()) {
            return "Error: Python code cannot be empty";
        }

        try (Context context = Context.newBuilder("python")
                .engine(engine)
                .allowAllAccess(false) // Security: restrict access by default
                .allowIO(false) // Disable file I/O for security
                .allowNativeAccess(false) // Disable native access for security
                .allowCreateProcess(false) // Disable process creation for security
                .allowHostAccess(true) // Allow access to host objects
                .build()) {

            log.debug("Executing Python code: {}", request.code);

            // Execute the Python code
            Value result = context.eval("python", request.code);

            // Convert result to string
            if (result.isNull()) {
                return "Execution completed with no return value";
            }

            // Handle different result types
            if (result.isString()) {
                return result.asString();
            }
            else if (result.isNumber()) {
                return String.valueOf(result.as(Object.class));
            }
            else if (result.isBoolean()) {
                return String.valueOf(result.asBoolean());
            }
            else if (result.hasArrayElements()) {
                // Convert array/list to string representation
                StringBuilder sb = new StringBuilder("[");
                long size = result.getArraySize();
                for (long i = 0; i < size; i++) {
                    if (i > 0) {
                        sb.append(", ");
                    }
                    Value element = result.getArrayElement(i);
                    sb.append(element.toString());
                }
                sb.append("]");
                return sb.toString();
            }
            else {
                // For other types, use toString()
                return result.toString();
            }
        }
        catch (PolyglotException e) {
            log.error("Error executing Python code", e);
            return "Error executing Python code: " + e.getMessage();
        }
        catch (Exception e) {
            log.error("Unexpected error executing Python code", e);
            return "Unexpected error: " + e.getMessage();
        }
    }

    /**
     * Request structure for the Python tool.
     */
    public static class PythonRequest {

        @JsonProperty(required = true)
        @JsonPropertyDescription("The Python code to execute")
        public String code;

        public PythonRequest() {
        }

        public PythonRequest(String code) {
            this.code = code;
        }
    }
}

4.3 AgentStaticLoader agent静态加载器,提供给spring-ai-alibaba提供聊天UI使用的。

java 复制代码
class AgentStaticLoader implements AgentLoader {
    private Map<String, BaseAgent> agents = new ConcurrentHashMap();

    public AgentStaticLoader() {
        super();
    }

    public AgentStaticLoader(BaseAgent... agents) {
        super();
        this.agents = (Map)Arrays.stream(agents).collect(Collectors.toUnmodifiableMap(Agent::name, Function.identity()));
    }

    @Nonnull
    public List<String> listAgents() {
        return this.agents.keySet().stream().toList();
    }

    public BaseAgent loadAgent(String name) {
        if (name != null && !name.trim().isEmpty()) {
            BaseAgent agent = (BaseAgent)this.agents.get(name);
            if (agent == null) {
                throw new NoSuchElementException("Agent not found: " + name);
            } else {
                return agent;
            }
        } else {
            throw new IllegalArgumentException("Agent name cannot be null or empty");
        }
    }
}

五、spring-ai-alibaba提供聊天UI启动后效果,如果没改端口,默认访问地址是:

Chat with you agent: http://localhost:8080/chatui/index.html

这里只展示执行python代码的案例; 1、还可以在聊天框输入"/文件的全路径/你的.txt" 文件,会展示txt文本的内容,如何调用参考这个; 2、也可以输入一个 file_path=/fullpath/your_new.txt content=你的文本内容,新增一个文件,并写内容)

相关推荐
天“码”行空7 分钟前
java面向对象的三大特性之一多态
java·开发语言·jvm
毕设源码-郭学长10 分钟前
【开题答辩全过程】以 基于SpringBoot框架的民俗文化交流与交易平台的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
水如烟15 分钟前
孤能子视角:关系性学习,“喂饭“的小孩认知
人工智能
徐_长卿18 分钟前
2025保姆级微信AI群聊机器人教程:教你如何本地打造私人和群聊机器人
人工智能·机器人
XyX——21 分钟前
【福利教程】一键解锁 ChatGPT / Gemini / Spotify 教育权益!TG 机器人全自动验证攻略
人工智能·chatgpt·机器人
l***217824 分钟前
SpringBoot Maven快速上手
spring boot·后端·maven
好大哥呀1 小时前
Java Web的学习路径
java·前端·学习
f***14771 小时前
SpringBoot实战:高效实现API限流策略
java·spring boot·后端
计算机毕设VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue动物园管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
on the way 1231 小时前
day06-SpringDI 依赖注入
java·spring