LCEL 與 Agent

LCEL 全名 LangChain Expression Language, 是一種描述 LangChain 架構的語言。最大的特徵就是看到 A = B | C | D 這種表示法 – 說明了 LCEL 串接多個可執行單元的特性,是 LangChain 的進階實現。而 ‘|’ 的理解和 Linux 的 pipe 很像,它是非同步的串接。

舉例來說,一個 LLM 的 LangChain 可以表示為:

chain = prompt | LLM model | Output Parser

依序做好這三件事:有方向性的提示、強大的大語言模型、友善的輸出格式,就可以提升使用者體驗。

但是顯然這樣還不太夠,比方說,LLM 需要查一筆它沒被訓練過的資料,在上面的 pipe 就無法做到。換個例子,我想幫 AI 助理取個名字,它也會說好。但是一轉眼什麼山盟海誓都忘光了!

顯然,我們要有個負責的 agent,把 function call 的能力加進來;而且 call 完之後,還要餵給 LLM 做 “人性化" 的自然語言潤飾。這是一個循環的路徑,直到 AI 判斷它不再需要任何外部工具,就可以結束迴圈,跟使用者報告最終結果了。

那麼這個 code 長什麼樣子? 首先把基本元件準備好:

# 初始化模型
llm = ChatOpenAI(model="gpt-4o", temperature=0)

# 將工具打包成列表
tools = [get_current_weather] # 以問天氣的函式為例

# 給出配合 function 的 prompt
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "你是天氣助理,請根據工具的結果來回答問題。"),
        ("placeholder", "{chat_history}"),  # 預留給歷史訊息
        ("human", "{input}"), # 真正的輸入
        ("placeholder", "{agent_scratchpad}"), # 於思考和記錄中間步驟
    ]
)

創建 agent

# 創建 Tool Calling Agent
# 將 LLM、Prompt 和 Tools 組合起來,處理 Function Calling 的所有複雜流程
agent = create_tool_calling_agent(llm, tools, prompt)

執行 agent

# 創建執行器, 跑 模型思考 -> 呼叫工具 -> 再次思考 -> 輸出答案的循環
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

設計人機介面

def run_langchain_example(user_prompt: str):
    print(f"👤 用戶提問: {user_prompt}")
    
    # 使用 invoke 運行 Agent,LangChain 會自動管理多輪 API 呼叫
    result = agent_executor.invoke({"input": user_prompt})
    
    print(f"🌟 最終答案: {result['output']}")

測試案例

# 示範一:Agent 判斷需要呼叫工具
run_langchain_example("請問新竹現在的天氣怎麼樣?請告訴我攝氏溫度。")

# 示範二:Agent 判斷不需要呼叫工具,直接回答
run_langchain_example("什麼是 LLM?請用中文簡短回答。")

這邊要留意的是,第一個問天氣的問題,LLM 顯然要用到 tool 去外面問,第二個問題不需要即時的資料,所以它自己回答就好。

意猶未盡的讀者可能好奇 function 長怎樣? 其實就很一般,主要的特色是用 @tool decorator。這樣可以獲得很多好處,最重要的一點是 agent 都認得它的 JSON 輸出,方便資料異步流動。

# LangChain 會自動將這個 Python 函數轉換成模型可理解的 JSON Schema
from langchain_core.tools import tool

@tool
def get_current_weather(location: str, unit: str = "celsius") -> str:
    if "新竹" in location or "hsinchu" in location.lower():
        return "風大啦! 新竹就是風大啦!"
    else:
        return f"我不知道 {location} 的天氣啦!"

另外,追根究柢的讀者可能想問,code 裡面怎麼沒看到 ‘|’? 它跑那裡去了? 沒錯,上面講的都是 agent,它比較進階,可以動態跑流程。反而 LCEL 只是 LangChain 的實行方式,它就是一個線性的 chain。

我們由奢返儉,回過頭來看 LCEL,它不能跟 agent 比,只能打敗傳統的 LangChain。標為紅色的是 LCEL 特色的 coding style。

def demonstrate_legacy_chain():
 
    # 定義模型
    llm = ChatOpenAI(temperature=0)
    
    # 定義 Prompt Template
    template = "Translate English text to Chinese: {text}"
    prompt = PromptTemplate(template=template, input_variables=["text"])
    
    # 建立 Chain (透過類別組合)
    # 缺點:語法較冗長,看不到資料流向,且 output 通常包含原始 meta data
    chain = LLMChain(llm=llm, prompt=prompt)
    
    # 執行
    input_text = "Make American Great Again!"
    result = chain.run(input_text)

VS

def demonstrate_lcel():
    
    # 定義模型
    model = ChatOpenAI(temperature=0)
    
    # 定義 Prompt Template, 使用更現代的 ChatPromptTemplate
    prompt = ChatPromptTemplate.from_template("Translate English text to Chinese: {text}")
    
    # 定義 Output Parser (將 AI Message 轉為純字串)
    output_parser = StrOutputParser()
    
    # 建立 Chain (使用 Pipe '|' 運算符)
    # 優點:Unix 風格管道,由左至右邏輯清晰,易於修改和擴展
    chain = prompt | model | output_parser
    
    # 執行
    input_text = "Make American Great Again!"
    result = chain.invoke({"text": input_text})

這兩個都是一次性的 Q&A。

  1. Functions, Tools and Agents with LangChain

用 Multi-LLM 解釋投資風險

Coursera 有一門新的課 [1], 由該公司老闆 Andrew 介紹 CrewAI 來講課. 主要是講多個 LLM 怎麼應用. 課程不長, 有 Lab, 沒證書. 看在老闆推薦的份上, 我也來蹭一下.

用最簡單的話來講, 它的技術就是叫每個 Agent 執行一個 task. 雖然大家平平都是 LLM, 但是指定了不同的角色, 每個 agent 就會各自專注在它的 task 上, 達到互相幫忙的結果. 當然每個 agent 的排列方式 (hierachy) 會影響他們共事的結果.

可不可 search 網路? 需不需要 human input, 可不可以非同步? 這些在 CrewAI 這家公司的 library 中都可以設定. 每個 agent 透過 memory 互相溝通, 因此即使不指定誰 (agent) 要傳訊息給誰 (other agents), 資料也可以共用.

有個 Lab 很好玩, 就是建立一個 crew 去分析買股票的風險. 它的架構是 Crew 叫 agent 做 task. Task 就只是明訂工作內容 (description) 和預期成果 (expect output), 然後註明給哪個 agent. Agent 要指定 role, goal, backstroy (工作指導), 標記可以用那些 tools? 標記可不可以餵資料給別人 (delegation), log 要多詳細 (verbose).

from crewai import Crew, Process
from langchain_openai import ChatOpenAI

# Define the crew with agents and tasks
financial_trading_crew = Crew(
    agents=[data_analyst_agent, 
            trading_strategy_agent, 
            execution_agent, 
            risk_management_agent],
    
    tasks=[data_analysis_task, 
           strategy_development_task, 
           execution_planning_task, 
           risk_assessment_task],
    
    manager_llm=ChatOpenAI(model="gpt-3.5-turbo", 
                           temperature=0.7),
    process=Process.hierarchical,
    verbose=True
)

Crew kickoff 之後, agent 就會去做事. 至於要做什麼? 寫在 input string 裡, 相當於一個 prompt. 舉例指定用 1000 元去買 Nvidia, 風險承受度中等, 應該如何操作? 在課程的例子中, 因為指定 process 是 hierachy. 所以叫第一個 agent 去做 data analysis, 它有 search 網路的 tool, 因此就會各個網站 search Nvidia 的新聞. 總結出 10 條. 交給下一棒 Trade agent.

Trade agent 的工作是要分析標的物的統計值, 它也有網路工具. 所以它也去找了一堆網站. 總結出 Nvidia 的評價.

Based on the information gathered from various analyst forecasts and recommendations, the average 12-month price target for NVDA is $130.68, with the highest target being $200.00 and the lowest at $90.00. The consensus rating for NVDA is “Strong Buy," supported by 38 buy ratings and 3 hold ratings. The stock has a current price of $135.58. The analysis suggests that there is a potential -3.61% downside from the current price based on the average price target. The historical performance of NVDA shows consistent outperformance relative to the industry.

接一下到了 execution agent. 它有甚麼大膽的創見嗎? 沒有. 即使它收到這麼明顯地看多訊息: Considering the historical performance and analyst forecasts, developing a trading strategy that aligns with the bullish sentiment towards NVDA could be a profitable approach, especially for day trading preferences.

它還是說我要上網查看看, 然後歸納出 5 點結論:

Execution Plan for NVDA:
1. Utilize historical performance data to identify key trends and patterns in NVDA’s stock price movements.
2. Implement a strategy that leverages the ‘Strong Buy’ recommendation and average 12-month price target of $130.68.
3. Monitor market trends and movements closely to capitalize on potential trading opportunities presented by NVDA’s growth potential.
4. Develop a risk management strategy that aligns with the user-defined risk tolerance (Medium) and trading preferences (Day Trading).
5. Regularly review and adjust the execution plan based on new market data and insights to optimize trading outcomes for NVDA.

接著回到 Crew. 它根據風險承受度為 Medium 這個條件, 再上網去跑一輪. 對每個網站的內容做一個小結論. 最後叫 risk management agent 彙總, 結果就是給安全牌 (因為風險承受度不高).

Overall, the risk analysis for NVDA’s trading strategies should focus on understanding the potential risks associated with each strategy, assessing the firm’s risk tolerance, and implementing appropriate safeguards to manage and mitigate risks effectively.

我認為畢竟 Crew 收到的指令就是風險承受度中等而已. 已經預設立場, 不用問 AI 也知道結果. 當我把風險承受度改為 Ultra High 重跑一次. 這次它的結論就變狠了! 建議了一些選擇權策略: Straddle Strategy、Iron Condor Strategy 、Long Call Butterfly Spread Strategy、LEAPS Contracts Strategy 等等.

這告訴我們兩件事.:

  1. CrewAI 使用 multi LLM 的功效很強大. 大家做完自己的事就交給同事 (co-worker), 各司其職. 可以用同一個 LLM 做出一群同事開會的效果!
  2. 你跟 AI 講我風險承受度低, AI 就叫你保守. 你說你不怕死, AI 就叫你玩選擇權. 這些不用問 AI, 應該是問施主你自己就好了.

[REF]

  1. https://www.coursera.org/learn/multi-ai-agent-systems-with-crewai/home/welcome