藉助AI助手快速解析LlamaIndex的Workflow設計與Java遷移

努力的小雨發表於2024-12-09

在前面的討論中,我們透過AI助手快速瀏覽並分析了LlamaIndex的核心原始碼及其視覺化部分。在上次的工作中,我們已基本完成了使用Java版本實現的視覺化部分,儘管在工作流(workflow)的分析上只是進行了初步探討。今天,我們將深入探討一個關鍵問題:能否將LlamaIndex在Python中的業務流程和核心程式碼,成功遷移並轉化為Java版本。

接下來,我們將直接進入正題。首先,我們回顧一下LlamaIndex的整體架構和核心功能,然後著手進行Java版本的開發實現。

Workflow

可能大家已經有些遺忘了之前的細節,因此僅透過文字描述並記錄下來,可能無法像視覺化圖示那樣快速有效地幫助我們回憶起關鍵內容。為了更清晰地梳理思路,並幫助大家更直觀地理解和回顧,我繪製一張簡要的總結框架圖。

簡要總結框架圖

首先,我們將從一個簡潔的角度,回顧幾個關鍵的核心類,並詳細分析它們各自的屬性和方法。如圖所示:

image

剩下的部分就是至關重要的業務流程類 workflow 了。為了確保我們對整體業務流程有一個清晰的認知,我們可以先對業務流程進行一個簡要的梳理。這裡不需要過多關注細節,細節部分可以透過檢視原始碼來進一步探討。

image

好的,接下來我們將逐一透過AI助手來幫助我們完成程式碼轉化的工作。雖然我們清楚地知道,AI的輸出可能無法達到100%的完美效果,但即便如此,藉助AI的輔助,至少可以大幅度提高效率,預計能夠節省大約50%的編碼時間。我們現在就開始吧。

設計

Event

同樣直接詢問助手即可。

image

接下來,我將根據AI助手提供的初步方案進行進一步的最佳化和調整。得到最終結果程式碼如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
 public abstract class  ToolEvent {

    /**
     * 事件id
     */
    private String eventId;

    /**
     * 事件name
     */
    private String eventName;

    /**
     * 儲存各節點資料,並暴露出方法供其他人呼叫
     */
    private Map<String,Object> eventData = new HashMap<>();

    public ToolEvent(Map<String, Object> params) {
        this.eventData.putAll(params);
    }

    public Object get(String key) {
        key = this.getClass().getSimpleName() + "." + key;
        if (this.eventData.containsKey(key)) {
            return this.eventData.get(key);
        } else {
            throw new IllegalArgumentException("No such key: " + key);
        }
    }

    public void set(String key, Object value) {
        key = this.getClass().getSimpleName() + "." + key;
        this.eventData.put(key, value);
    }

    public boolean containsKey(String key) {
        key = this.getClass().getSimpleName() + "." + key;
        return this.eventData.containsKey(key);
    }

    public Set<String> keySet() {
        return this.eventData.keySet();
    }

    public Collection<Object> values() {
        return this.eventData.values();
    }

    public Set<Map.Entry<String, Object>> entrySet() {
        return this.eventData.entrySet();
    }

    public int size() {
        return this.eventData.size();
    }

    public boolean isEmpty() {
        return this.eventData.isEmpty();
    }

    public void clear() {
        this.eventData.clear();
    }

    public Map<String, Object> toMap() {
        return this.eventData;
    }

    /**
     * eventName預設為類名
     */
    public String getEventName() {
        if (this.eventName == null) {
            this.eventName = this.getClass().getSimpleName();
        }
        return this.eventName;
    }
}

可以看到,在此程式碼中我實現的是一個最基礎的版本。我們並不打算在初期階段實現所有功能,而是先著手於構建一個簡化版的工作流系統。這樣做的目的是先實現一個基礎的可執行版本,再在此基礎上進行最佳化和改進,以便最終得到一個更加高效且符合需求的解決方案。

接下來,實現工作流中的開始節點和結束節點的生成,這一過程同樣相對簡單。以下是相應的程式碼實現:

@Data
@NoArgsConstructor
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class StartEvent extends ToolEvent {

    private String eventName = "start";
    public StartEvent(Map<String, Object> params) {
        super(params);
    }
}

@Data
@NoArgsConstructor
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class StopEvent extends ToolEvent {

    private String eventName = "end";
    /**
     * 結果返回
     */
    private String result;

    public StopEvent(String params) {
        result = params;
    }
}

LlamaIndex的事件封裝了很多其他功能和細節,這些內容雖然很有用,但在當前階段我們先不深入探討。

Step註解

然後我們看下註解,這部分也可以詢問下AI助手,如圖所示:

image

不過Python的裝飾器並不和Java註解一樣,所以我們先來自己實現一下。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Step {

//    String eventName();
    Class<? extends ToolEvent>[] acceptedEvents();
    int numWorkers() default 10;

//    Class<?>[]  returnTypes();
//    RetryPolicy retryPolicy() default RetryPolicy.DEFAULT;
}

workflow

接下來,我們將著手實現工作流的主要流程。這一部分相對較為複雜,主要因為涉及的業務流程非常龐大且複雜,因此需要一定的時間和精力進行處理。為了簡化我們的工作,首先我們可以參考AI助手的實現方式,透過分析其設計思路和工作原理,幫助我們更好地理解如何進行具體的實現。

在此基礎上,我們再根據實際需求對流程進行必要的裁剪和最佳化。如下圖所示:

image

最後,這部分程式碼有些多,我就簡單的將主要流程寫下來。程式碼如下:

@Data
@Slf4j
public abstract class Workflow {

    /**
     * 工作流超時時間
     */
    private  int timeout = 10;
    /**
     * 是否輸出詳細日誌
     */
    private  boolean verbose = false;

    /**
     * 校驗開關
     */
    private boolean validation = false;
    /**
     * 校驗開關
     */
    private boolean showUI = false;
    
    /**
     * 1:掃描當前類的所有帶有 @Step 註解的方法
     * 2:根據步驟順序,依次執行各個步驟
     */
    public String run(String jsonString) throws IOException{}
    /**
     *初始化工作流
     */
    private WorkflowContext initialContext() {}

WorkflowContext

最關鍵的因素在於工作流上下文的設計,因為在這種架構下,所有節點都能夠共享全域性變數。這一特性保證了工作流中不同節點之間的資料傳遞和協調,從而提高了整個系統的靈活性和便利性。如果沒有這樣的共享機制,工作流的效率和可操作性將大大降低,失去其原本的優勢。

我們先去詢問下AI助手如何實現。如圖所示:

image

由於LlamaIndex提供了許多功能,因此其實現顯得相對複雜。為了簡化開發過程,我們決定剔除一些不必要的功能,比如類的序列化,這一功能主要用於恢復和載入工作流。然而,我們的目標是實現一個最基本且可行的工作流。最終程式碼如下:

@Slf4j
@Data
public class WorkflowContext {
    /**
     * 是否是單步模式
     */
    private boolean stepwise;
    /**
     * 是否正在執行
     */
    private boolean isRunning;

    /**
     * 當前執行的事件
     */
    private ToolEvent stepEventHolding;

    /**
     * 事件佇列:k:方法名,v:佇列
     */
    private Map<String, ArrayBlockingQueue<ToolEvent>> eventQueue;

    private List<Thread> tasks = new ArrayList<>();

    private Map<String,Object> globalContext;

    private String result;
    //畫圖
    private Graph graph = new MultiGraph("workflow");
    public WorkflowContext(boolean stepwise){
          System.setProperty("org.graphstream.ui", "swing");
          this.stepwise = stepwise;
          this.isRunning = false;
          this.eventQueue = new ConcurrentHashMap<>();
          this.stepEventHolding = null;
          this.globalContext = new ConcurrentHashMap<>();
          this.result = null;
          //新增開始和結束節點
          Node nodeA = graph.addNode("start");
          Node nodeB = graph.addNode("end");
      }
      
    public void addThread(Thread thread) {
        tasks.add(thread);
    }
    
    public void sendEvent(ToolEvent value) {}

我們去除了一些東西,加了一個上一章節我們討論的流的視覺化。並且需要實現釋出事件功能。

WorkflowHandler

最後加一個處理類,同樣直接問一下AI助手,幫助我們去實現一下基本業務邏輯,如圖所示:

image

然後我把所有沒有用的邏輯全都去除掉,最後剩下這些程式碼,如下所示:

@Slf4j
@Data
@AllArgsConstructor
public class WorkflowHandler {

    private WorkflowContext context;
    
    public void handleTask(int timeout){}

接下來,我們需要填充基本的業務邏輯部分,這一階段的工作主要涉及實現具體的功能和處理流程,確保工作流能夠按照預期正常執行。由於這部分程式碼涉及到特定的業務需求和內部實現細節,因此暫時不公開。工作流啟動日誌如下:

image

最終的效果如圖所示:

image

總結

透過以上的分析和實踐,我們成功地對LlamaIndex的核心功能進行了回顧,並逐步將其Python版的業務流程和核心程式碼遷移到Java實現。儘管在過程中遇到了一些挑戰,如工作流的複雜性、事件和註解的差異等,但藉助AI助手的輔助,我們能夠高效地完成了程式碼轉換和初步實現。

接下來的步驟,將是基於當前實現,進一步完善各個模組,最佳化工作流的執行效率,提升系統的可靠性和擴充套件性。


我是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。我熱愛技術交流與分享,對開源社群充滿熱情。同時也是一位騰訊雲創作之星、阿里雲專家博主、華為云云享專家、掘金優秀作者。

💡 我將不吝分享我在技術道路上的個人探索與經驗,希望能為你的學習與成長帶來一些啟發與幫助。

🌟 歡迎關注努力的小雨!🌟

相關文章