一個真正強大的資料函式庫助手不僅能執行查詢,還應該能夠生成視覺化圖表並將結果儲存到檔案系統中。為此,我們需要擴充套件代理的工具箱。

增加Python REPL和檔案管理工具

我們需要增加兩種新工具:

  1. PythonREPLTool:允許代理執行Python程式碼,用於生成圖表
  2. FileManagementToolkit:允許代理與檔案系統互動,儲存生成的圖表

以下是如何增加這些工具:

import os
from langchain_experimental.tools.python.tool import PythonREPLTool
from langchain_experimental.python import PythonREPL
from langchain.agents.agent_toolkits import FileManagementToolkit

# 設定工作目錄
working_directory = os.getcwd()

# 建立檔案管理工具
tools = FileManagementToolkit(
    root_dir=str(working_directory),
    selected_tools=["read_file", "write_file", "list_directory"],
).get_tools()

# 增加Python REPL工具
tools.append(PythonREPLTool())

# 增加SQL資料函式庫工具
tools.extend(SQLDatabaseToolkit(db=db, llm=llm).get_tools())

這段程式碼擴充套件了我們的工具集:

  1. 首先取得當前工作目錄
  2. 建立一個檔案管理工具包,限制它只能使用讀取、寫入檔案和列出目錄功能
  3. 增加Python REPL工具,允許執行Python程式碼
  4. 最後加入之前的SQL資料函式庫工具

這樣,我們的代理就擁有了查詢資料函式庫、執行Python程式碼和管理檔案的能力,為開發全功能資料函式庫助手奠定了基礎。

建立多功能代理

由於我們現在使用了多種工具,不能再使用專用的SQL代理,而需要一個能夠靈活使用多種工具的通用代理:

from langchain.chat_models import ChatOpenAI
from langchain.agents import initialize_agent, Tool
from langchain.agents import AgentType

# 使用聊天模型,更適合結構化對話
model = ChatOpenAI()

# 初始化能夠使用多種工具的代理
agent = initialize_agent(
    tools, 
    model, 
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, 
    verbose=True
)

這裡我們使用了ChatOpenAI模型,它更適合處理結構化的對話式任務。代理型別選擇了STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,這是一種能夠處理多種工具輸入的通用代理型別。“Zero-shot"表示它不需要特定領域的訓練範例就能完成任務,“React"則指其能夠思考(Reason)並根據思考結果採取行動(Act)。

生成銷售資料視覺化圖表

現在,讓我們測試這個增強型代理的能力,要求它生成一個顯示銷售額前5名國家的柱狀圖:

agent.run("generate a matplotlib bar chart of the top 5 countries for sales from the chinook database. Save the output in the current working directory as figure.png")

這個指令會讓代理:

  1. 查詢資料函式庫取得前5名國家的銷售資料
  2. 使用Python和matplotlib生成柱狀圖
  3. 將圖表儲存為figure.png檔案

執行後,代理會依序使用不同工具完成這個複雜任務。首先,它會使用SQL工具查詢資料:

Action: sql_db_query
Action Input: SELECT c.country, SUM(i.total) AS total_sales FROM customer c JOIN invoice i ON c.customer_id = i.customer_id GROUP BY c.country ORDER BY total_sales DESC LIMIT 5
Observation: [('USA', 523.0600000000003), ('Canada', 303.9599999999999), ('Brazil', 190.09999999999994), ('France', 177.59999999999997), ('Germany', 156.48)]

然後

def find_duplicates(arr):
    duplicates = []
    for i in range(len(arr)):
        for j in range(i+1, len(arr)):
            if arr[i] == arr[j] and arr[i] not in duplicates:
                duplicates.append(arr[i])
    return duplicates

"””

print(llm(prompt))



這段程式碼定義了一個函式 `find_duplicates`,用於找出陣列中的重複元素。它使用了雙層迴圈來比較每個元素與其後的所有元素,如果發現重複與該元素尚未被增加到結果列表中,則將其加入。這種實作方式的時間複雜度為 O(n²),在大型陣列上效能較差。

讓我們看看 CodeLlama 如何最佳化這段程式碼:

```python
def find_duplicates(arr):
    seen = set()
    duplicates = set()
    
    for item in arr:
        if item in seen:
            duplicates.add(item)
        else:
            seen.add(item)
            
    return list(duplicates)

這個最佳化後的版本利用了 Python 的集合(set)資料結構,將時間複雜度從 O(n²) 降低到了 O(n)。演算法只需遍歷陣列一次,使用 seen 集合來追蹤已經出現過的元素,使用 duplicates 集合來儲存重複的元素。

在 Python 中,集合的成員檢查(item in seen)是一個 O(1) 的操作,這使得整個演算法的效率大幅提高。最後,將結果集合轉換為列表回傳給呼叫者。

這種最佳化特別適用於處理大型資料集,因為它的執行時間隨著輸入大小線性增長,而不是平方增長。

讓我們繼續看看如何使用 CodeLlama 處理其他程式碼相關任務:

prompt = """
Write a Python function that takes a list of integers and returns a new list containing only the prime numbers from the original list.
"""

print(llm(prompt))

在這個例子中,我們要求 CodeLlama 建立一個函式,從整數列表中篩選出質數。這需要兩個部分的知識:如何判斷一個數是否為質數,以及如何從列表中篩選元素。

def is_prime(n):
    """Check if a number is prime."""
    if n <= 1:
        return False
    if n <= 3:
        return True
    if n % 2 == 0 or n % 3 == 0:
        return False
    i = 5
    while i * i <= n:
        if n % i == 0 or n % (i + 2) == 0:
            return False
        i += 6
    return True

def filter_primes(numbers):
    """Return a list containing only the prime numbers from the input list."""
    return [num for num in numbers if is_prime(num)]

# Example usage
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
prime_numbers = filter_primes(numbers)
print(prime_numbers)  # Output: [2, 3, 5, 7, 11, 13]
__CODE_BLOCK_8__python
prompt = """
Implement a function to find the longest common subsequence of two strings. For example, the longest common subsequence of "abcde" and "ace" is "ace".
"""

print(llm(prompt))
__CODE_BLOCK_9__python
def longest_common_subsequence(text1, text2):
    """
    Find the longest common subsequence of two strings.
    
    Args:
        text1 (str): First string
        text2 (str): Second string
        
    Returns:
        str: The longest common subsequence
    """
    m, n = len(text1), len(text2)
    
    # Create a table to store lengths of LCS for all subproblems
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    # Fill the dp table
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if text1[i-1] == text2[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    
    # Reconstruct the LCS from the dp table
    lcs = []
    i, j = m, n
    while i > 0 and j > 0:
        if text1[i-1] == text2[j-1]:
            lcs.append(text1[i-1])
            i -= 1
            j -= 1
        elif dp[i-1][j] > dp[i][j-1]:
            i -= 1
        else:
            j -= 1
    
    return ''.join(reversed(lcs))

# Example usage
text1 = "abcde"
text2 = "ace"
print(longest_common_subsequence(text1, text2))  # Output: "ace"

CodeLlama 生成了一個標準的動態規劃解法,這是解決 LCS 問題最優的方法:

  1. 首先建立一個二維 DP 表格,其中 dp[i][j] 表示 text1 的前 i 個字元和 text2 的前 j 個字元的最長共同子序列長度

  2. 填充 DP 表格的規則:

    • 如果兩個字元相同 (text1[i-1] == text2[j-1]),則 dp[i][j] = dp[i-1][j-1] + 1
    • 否則,取左邊或上方的最大值:dp[i][j] = max(dp[i-1][j], dp[i][j-1])
  3. 最後,從填充好的 DP 表格反向重建最長共同子序列

這是一個非常專業的解法,時間複雜度為 O(m×n),空間複雜度也為 O(m×n),其中 m 和 n 是兩個字元串的長度。CodeLlama 不僅生成了正確的演算法,還提供了完整的註解和範例使用方式,展示了它在處理演算法問題上的強大能力。

現在讓我們使用 StarCoder 模型,看看它如何處理程式碼生成任務。StarCoder 是專為程式碼設計的大模型語言,讓我們探索它的能力:

使用 StarCoder 處理程式碼相關任務

StarCoder 是由 Hugging Face 和 ServiceNow 合作開發的開放原始碼程式碼大模型語言。它是在超過 8000 億個程式碼 token 上訓練的,支援超過 80 種程式語言,包括程式碼完成、生成和理解等任務。

StarCoder 具有 15B 引數,擁有 8K token 的上下文視窗,並支援填充功能,使其能夠在程式碼片段中間生成內容。讓我們探索 StarCoder 的能力:

repo_id = "bigcode/starcoder"
llm = HuggingFaceHub(
    repo_id=repo_id, 
    model_kwargs={"temperature": 0.2, "max_new_tokens": 1000}
)

prompt = """
Create a React component for a responsive navigation bar with a logo, menu items, and a mobile toggle.
"""

print(llm(prompt))

讓我們看看 StarCoder 如何處理前端開發任務,特別是 React 元件的建立:

import React, { useState } from 'react';
import './Navbar.css';

const Navbar = ({ logo, menuItems }) => {
  const [isOpen, setIsOpen] = useState(false);

  const toggleMenu = () => {
    setIsOpen(!isOpen);
  };

  return (
    <nav className="navbar">
      <div className="navbar-container">
        <div className="navbar-logo">
          <img src={logo} alt="Logo" />
        </div>
        
        <div className="navbar-toggle" onClick={toggleMenu}>
          <span className={isOpen ? 'toggle-icon open' : 'toggle-icon'}></span>
        </div>
        
        <ul className={isOpen ? 'navbar-menu active' : 'navbar-menu'}>
          {menuItems.map((item, index) => (
            <li key={index} className="navbar-item">
              <a href={item.url} className="navbar-link">
                {item.title}
              </a>
            </li>
          ))}
        </ul>
      </div>
    </nav>
  );
};

export default Navbar;

/* CSS file: Navbar.css */
.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 2rem;
  background-color: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
}

.navbar-container {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
}

.navbar-logo img {
  height: 40px;
}

.navbar-menu {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
}

.navbar-item {
  margin-left: 2rem;
}

.navbar-link {
  color: #333;
  text-decoration: none;
  font-weight: 500;
  transition: color 0.3s ease;
}

.navbar-link:hover {
  color: #007bff;
}

.navbar-toggle {
  display: none;
}

@media screen and (max-width: 768px) {
  .navbar-toggle {
    display: block;
    cursor: pointer;
    width: 30px;
    height: 30px;
    position: relative;
  }
  
  .toggle-icon {
    display: block;
    position: relative;
    width: 100%;
    height: 3px;
    background-color: #333;
    transition: all 0.3s ease;
  }
  
  .toggle-icon::before,
  .toggle-icon::after {
    content: '';
    position: absolute;
    width: 100%;
    height: 3px;
    background-color: #333;
    transition: all 0.3s ease;
  }
  
  .toggle-icon::before {
    transform: translateY(-10px);
  }
  
  .toggle-icon::after {
    transform: translateY(10px);
  }
  
  .toggle-icon.open {
    background-color: transparent;
  }
  
  .toggle-icon.open::before {
    transform: rotate(45deg);
  }
  
  .toggle-icon.open::after {
    transform: rotate(-45deg);
  }
  
  .navbar-menu {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    background-color: #fff;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    padding: 1rem 0;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease;
  }
  
  .navbar-menu.active {
    max-height: 300px;
  }
  
  .navbar-item {
    margin: 0.5rem 2rem;
  }
}

StarCoder 生成了一個完整的 React 導航欄元件,包括:

  1. React 元件結構

    • 使用 React hooks (useState) 來管理導航欄的開關狀態
    • 接受 logomenuItems 作為引數,提高元件的可重用性
    • 實作了切換選單的功能
  2. 回應式設計

    • 在桌面版顯示完整選單
    • 在移動裝置上轉換為漢堡選單按鈕和下拉選單
    • 使用媒體查詢來處理不同螢幕尺寸
  3. CSS 樣式

    • 提供了完整的 CSS 檔案,包含所有必要的樣式
    • 實作了漢堡選單圖示的動畫效果
    • 處理了選單展開/收起的過渡效果

這個元件的實作非常專業,展示了 StarCoder 對前端框架和回應式設計的深入理解。它不僅提供了功能完整的程式碼,還考慮了使用者經驗和設計

遞迴與迭代的藝術:Python演算法實戰

在解決複雜程式問題時,常常需要在遞迴與迭代方法之間做出選擇。這兩種方法各有優缺點,選擇哪一種取決於問題的性質、效能需求和可讀性考量。今天我將探討這兩種方法在實作經典演算法時的應用,特別聚焦於階乘計算和Fibonacci序列。

階乘函式的多種實作方式

階乘是數學中的基本概念,定義為所有小於等於該數的正整數的乘積。例如,5的階乘(5!)等於5×4×3×2×1=120。在程式設計中,實作階乘計算有多種方法,最常見的是迭代和遞迴。

迭代實作階乘

首先讓我們看看迭代方式實作的階乘函式:

def factorial(n):
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

# 使用範例:
n = 5
print("Factorial of", n, "is", factorial(n))  # 輸出: Factorial of 5 is 120

這個迭代實作使用了一個簡單的迴圈來計算階乘。我們從1開始,逐步乘到n,將每次的乘積累積在result變數中。這種方法直觀明瞭,執行效率高,不會有遞迴可能帶來的堆積積疊溢位問題。對於大多數實際應用場景,這是計算階乘的首選方法。

遞迴實作階乘

接下來看看遞迴方式的實作:

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n - 1)

# 使用範例:
n = 5
print("Factorial of", n, "is", factorial(n))  # 輸出: Factorial of 5 is 120

遞迴方法的特點是函式呼叫自身。這裡的實作根據階乘的數學定義:n! = n × (n-1)!,當n=0時,0! = 1(這是階乘的基本定義)。

遞迴版本的程式碼更接近階乘的數學定義,因此在可讀性方面有優勢。但需要注意的是,Python的遞迴深度有限制(預設為1000),因此對於非常大的n值,這種方法可能會導致堆積積疊溢位錯誤。此外,每次遞迴呼叫都會在堆積積疊上建立新的函式框架,這可能導致顯著的記憶體開銷。

Fibonacci數列的實作挑戰

Fibonacci數列是另一個經典的數學序列,定義為每個數等於前兩個數之和,起始於0和1。例如,前10個Fibonacci數是:0, 1, 1, 2, 3, 5, 8, 13, 21, 34。

讓我們來看看實作Fibonacci數列的不同方法及其效能特點。

遞迴實作Fibonacci

最直觀的方法是使用遞迴:

def fibonacci_recursive(n):
    if n <= 1:
        return n
    else:
        return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)

# 使用範例
n = 10
print(f"The {n}th Fibonacci number is: {fibonacci_recursive(n)}")  # 輸出: The 10th Fibonacci number is: 55
__CODE_BLOCK_15__python
def fibonacci_memoization(n, memo={}):
    if n in memo:
        return memo[n]
    if n <= 1:
        return n
    memo[n] = fibonacci_memoization(n-1, memo) + fibonacci_memoization(n-2, memo)
    return memo[n]

# 使用範例
n = 100
print(f"The {n}th Fibonacci number is: {fibonacci_memoization(n)}")  # 能高效計算大數
__CODE_BLOCK_16__python
def fibonacci_memoization_improved(n):
    memo = {}
    
    def fib(n):
        if n in memo:
            return memo[n]
        if n <= 1:
            return n
        memo[n] = fib(n-1) + fib(n-2)
        return memo[n]
    
    return fib(n)
__CODE_BLOCK_17__python
def fibonacci_iterative(n):
    if n <= 1:
        return n
    a, b = 0, 1
    for i in range(2, n+1):
        a, b = b, a + b
    return b

# 使用範例
n = 1000
print(f"The {n}th Fibonacci number is: {fibonacci_iterative(n)}")  # 可以高效計算非常大的n
__CODE_BLOCK_18__python
def remove_non_ascii(string):
    return string.encode('ascii', 'ignore').decode('utf-8')

# 使用範例
text = "Héllö Wörld! 你好,世界!"
print(remove_non_ascii(text))  # 輸出: "Hll Wrld! ,!"
__CODE_BLOCK_19__python
# 錯誤的函式
import random
a = random.randint(1, 12)
b = random.randint(1, 12)
for i in range(10):
    question = "What is " + a + " x " + b + " ? "
    answer = input(question)
    if answer = a * b:
        print (Well done!)
    else:
        print("No.")
__CODE_BLOCK_20__python
import random
for i in range(10):
    a = random.randint(1, 12)
    b = random.randint(1, 12)
    question = "What is " + str(a) + " x " + str(b) + " ? "
    answer = input(question)
    if int(answer) == a * b:
        print("Well done!")
    else:
        print("No.")
__CODE_BLOCK_21__python
def longest_unique_substring(s):
    if not s:
        return ""
    
    # 使用字典記錄每個字元最後出現的位置
    char_dict = {}
    max_length = 0
    start_index = 0
    result = ""
    
    for i, char in enumerate(s):
        # 如果字元已經在當前視窗中出現過,更新視窗起始位置
        if char in char_dict and start_index <= char_dict[char]:
            start_index = char_dict[char] + 1
        # 如果當前視窗更長,更新結果
        elif i - start_index + 1 > max_length:
            max_length = i - start_index + 1
            result = s[start_index:i+1]
        
        # 更新字元最後出現的位置
        char_dict[char] = i
    
    return result

# 使用範例
test_string = "abcabcbb"
print(f"Longest substring with unique characters in '{test_string}': {longest_unique_substring(test_string)}")
# 輸出: Longest substring with unique characters in 'abcabcbb': abc

這個演算法使用了滑動視窗技術和雜湊表(Python的字典)來跟蹤字元的位置。核心思想是維護一個視窗,該視窗包含不重複的字元:

  1. 遍歷字元串中的每個字元。
  2. 如果字元已在視窗中出現過,透過更新視窗的起始位置來移除重複字元。
  3. 檢查當前視窗是否比之前找到的最長視窗更長,如果是,則更新結果。
  4. 更新字元最後出現的位置。

這個演算法的時間複雜度為O(n),其中n是字元串的長度,因為它只需要遍歷字元串一次。空間複雜度為O(min(m, n)),其中m是字元集的大小,在最壞情況下需要儲存所有唯一字元的位置。

選擇適合的模型進行程式

善用語言模型實作人工智慧程式碼生成與問題求解

在現代軟體開發環境中,大模型語言(LLM)已經成為提升開發效率的關鍵工具。這些強大的AI模型不僅能夠生成程式碼,還能理解複雜問題並提供解決方案。作為一位資深開發者,我發現將LLM與LangChain等框架結合,能夠顯著改變我們編寫和理解程式碼的方式。