在前面的討論中,我們透過AI助手快速瀏覽並分析了LlamaIndex的核心原始碼及其視覺化部分。在上次的工作中,我們已基本完成了使用Java版本實現的視覺化部分,儘管在工作流(workflow)的分析上只是進行了初步探討。今天,我們將深入探討一個關鍵問題:能否將LlamaIndex在Python中的業務流程和核心程式碼,成功遷移並轉化為Java版本。
接下來,我們將直接進入正題。首先,我們回顧一下LlamaIndex的整體架構和核心功能,然後著手進行Java版本的開發實現。
Workflow
可能大家已經有些遺忘了之前的細節,因此僅透過文字描述並記錄下來,可能無法像視覺化圖示那樣快速有效地幫助我們回憶起關鍵內容。為了更清晰地梳理思路,並幫助大家更直觀地理解和回顧,我繪製一張簡要的總結框架圖。
簡要總結框架圖
首先,我們將從一個簡潔的角度,回顧幾個關鍵的核心類,並詳細分析它們各自的屬性和方法。如圖所示:
剩下的部分就是至關重要的業務流程類 workflow
了。為了確保我們對整體業務流程有一個清晰的認知,我們可以先對業務流程進行一個簡要的梳理。這裡不需要過多關注細節,細節部分可以透過檢視原始碼來進一步探討。
好的,接下來我們將逐一透過AI助手來幫助我們完成程式碼轉化的工作。雖然我們清楚地知道,AI的輸出可能無法達到100%的完美效果,但即便如此,藉助AI的輔助,至少可以大幅度提高效率,預計能夠節省大約50%的編碼時間。我們現在就開始吧。
設計
Event
同樣直接詢問助手即可。
接下來,我將根據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助手,如圖所示:
不過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助手的實現方式,透過分析其設計思路和工作原理,幫助我們更好地理解如何進行具體的實現。
在此基礎上,我們再根據實際需求對流程進行必要的裁剪和最佳化。如下圖所示:
最後,這部分程式碼有些多,我就簡單的將主要流程寫下來。程式碼如下:
@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助手如何實現。如圖所示:
由於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助手,幫助我們去實現一下基本業務邏輯,如圖所示:
然後我把所有沒有用的邏輯全都去除掉,最後剩下這些程式碼,如下所示:
@Slf4j
@Data
@AllArgsConstructor
public class WorkflowHandler {
private WorkflowContext context;
public void handleTask(int timeout){}
接下來,我們需要填充基本的業務邏輯部分,這一階段的工作主要涉及實現具體的功能和處理流程,確保工作流能夠按照預期正常執行。由於這部分程式碼涉及到特定的業務需求和內部實現細節,因此暫時不公開。工作流啟動日誌如下:
最終的效果如圖所示:
總結
透過以上的分析和實踐,我們成功地對LlamaIndex的核心功能進行了回顧,並逐步將其Python版的業務流程和核心程式碼遷移到Java實現。儘管在過程中遇到了一些挑戰,如工作流的複雜性、事件和註解的差異等,但藉助AI助手的輔助,我們能夠高效地完成了程式碼轉換和初步實現。
接下來的步驟,將是基於當前實現,進一步完善各個模組,最佳化工作流的執行效率,提升系統的可靠性和擴充套件性。
我是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。我熱愛技術交流與分享,對開源社群充滿熱情。同時也是一位騰訊雲創作之星、阿里雲專家博主、華為云云享專家、掘金優秀作者。
💡 我將不吝分享我在技術道路上的個人探索與經驗,希望能為你的學習與成長帶來一些啟發與幫助。
🌟 歡迎關注努力的小雨!🌟