一個真正強大的資料函式庫助手不僅能執行查詢,還應該能夠生成視覺化圖表並將結果儲存到檔案系統中。為此,我們需要擴充套件代理的工具箱。
增加Python REPL和檔案管理工具
我們需要增加兩種新工具:
- PythonREPLTool:允許代理執行Python程式碼,用於生成圖表
- 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())
這段程式碼擴充套件了我們的工具集:
- 首先取得當前工作目錄
- 建立一個檔案管理工具包,限制它只能使用讀取、寫入檔案和列出目錄功能
- 增加Python REPL工具,允許執行Python程式碼
- 最後加入之前的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")
這個指令會讓代理:
- 查詢資料函式庫取得前5名國家的銷售資料
- 使用Python和matplotlib生成柱狀圖
- 將圖表儲存為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 問題最優的方法:
首先建立一個二維 DP 表格,其中
dp[i][j]
表示text1
的前 i 個字元和text2
的前 j 個字元的最長共同子序列長度填充 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])
- 如果兩個字元相同 (
最後,從填充好的 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 導航欄元件,包括:
React 元件結構:
- 使用 React hooks (
useState
) 來管理導航欄的開關狀態 - 接受
logo
和menuItems
作為引數,提高元件的可重用性 - 實作了切換選單的功能
- 使用 React hooks (
回應式設計:
- 在桌面版顯示完整選單
- 在移動裝置上轉換為漢堡選單按鈕和下拉選單
- 使用媒體查詢來處理不同螢幕尺寸
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的字典)來跟蹤字元的位置。核心思想是維護一個視窗,該視窗包含不重複的字元:
- 遍歷字元串中的每個字元。
- 如果字元已在視窗中出現過,透過更新視窗的起始位置來移除重複字元。
- 檢查當前視窗是否比之前找到的最長視窗更長,如果是,則更新結果。
- 更新字元最後出現的位置。
這個演算法的時間複雜度為O(n),其中n是字元串的長度,因為它只需要遍歷字元串一次。空間複雜度為O(min(m, n)),其中m是字元集的大小,在最壞情況下需要儲存所有唯一字元的位置。
選擇適合的模型進行程式
善用語言模型實作人工智慧程式碼生成與問題求解
在現代軟體開發環境中,大模型語言(LLM)已經成為提升開發效率的關鍵工具。這些強大的AI模型不僅能夠生成程式碼,還能理解複雜問題並提供解決方案。作為一位資深開發者,我發現將LLM與LangChain等框架結合,能夠顯著改變我們編寫和理解程式碼的方式。