Web-和机器学习的人工智能辅助变成-七-

49 阅读39分钟

Web 和机器学习的人工智能辅助变成(七)

原文:annas-archive.org/md5/f5ce3ca93c7ecd74cff0d3ae335a95cc

译者:飞龙

协议:CC BY-NC-SA 4.0

第二十章:利用 GitHub Copilot 提高效率

简介

到目前为止,你一直在使用书中开头介绍的关于 GitHub Copilot 和 ChatGPT 的知识。这些基础知识足以教你如何编写提示并接受它们。这也足以让你开始着手解决机器学习、数据科学和 Web 开发的问题。在 Web 开发的情况下,你也发现 Copilot 在处理现有代码库时是一个高效的工具。在本章中,我们希望将你的 AI 工具知识提升到下一个层次,因为还有更多你可能想要利用的功能。

有很多方法可以提高效率;你将在本章后面看到,Copilot 中有一些功能可以让你构建文件,你还将了解更多关于你的工作空间,甚至作为编辑器的 Visual Studio Code 的信息,这些都是节省时间的功能。本章将介绍一些最重要的功能。

在本章中,我们将:

  • 学习如何使用 Copilot 生成代码。

  • 使用 Copilot 命令来自动化任务,例如生成一个新项目。

  • 应用调试和故障排除代码的技术。

  • 使用 Copilot 审查和优化代码。

代码生成和自动化

在其核心,Copilot 是一个代码生成器。它可以为你生成文本,这些文本可以是文档或源代码的一部分。

使用 Copilot 生成代码有两种主要方式:

  • 通过提示作为注释的 Copilot 活动编辑器。

  • 允许你输入提示的 Copilot 聊天。

Copilot 的活动编辑器

当你在活动编辑器中时,Copilot 可以生成代码。要生成代码,你需要以注释的形式编写一个提示。在你的编程语言代码中添加注释有不同的方式。例如,在 JavaScript 和 C# 中,你使用 //,而在 HTML 中你会输入 <!--

让我们看看一个例子:

[提示]

创建一个名为 add 的新函数,它接受两个参数。

[提示结束]

//Create a new function called add that takes two parameters 

上面,我们有一个作为注释的提示。Copilot 会查看这个提示,并尝试根据它生成代码。在这个阶段,Copilot 会将生成的代码作为“建议”发送回来,你可以使用 Tab 键接受它。在编辑器中看起来是这样的:

// Create a new function called add that takes two parameters
function add(num1, num2) {
  throw new Error('Function not implemented.');
} 

到目前为止,你没有收到任何建议。你有几个选择:

  • 你可以按 Return 键获取更多建议。

  • 开始输入你想要生成的代码的一部分,Copilot 将根据你输入的内容建议代码。

Copilot 聊天

Copilot Chat 是一种通过聊天界面与 Copilot 交互的方式。要激活 Copilot Chat,点击 Visual Studio Code(或 VS Code)左下角的图标,这将打开一个聊天窗口。

图 20.1:显示 Visual Studio Code 中的 Copilot 聊天“聊天”图标左侧菜单

点击上面的聊天图标后,将显示下面的图片。

聊天窗口

图 20.2:上图显示了您在 Visual Studio Code 左侧菜单中点击聊天图标时打开的聊天窗口

让我们尝试根据之前的相同提示生成一些代码。让我们使用聊天窗口中的输入文本字段并输入提示:

[提示]

创建一个名为 add 的新函数,它接受两个参数

[指令结束]

输出分为两部分,一个是文本指令,另一个是代码建议。首先,文本指令:

[提示响应]

当然,这是一个名为 add 的函数的示例,它接受两个参数并返回它们的和:

function add(a, b) {
  return a + b;
} 

[响应结束]

它甚至显示了如何使用该函数的示例:

const result = add(2, 3);
console.log(result); // Output: 5 

Copilot 命令

如果您在聊天窗口中开始输入 /,您将看到可以使用 Copilot 的命令列表。

图 20.3:可用命令

有很多可用的命令,在本章中我们将关注其中的一些。

创建笔记本

使用此命令,您可以创建一个新的笔记本。笔记本是一个您可以存储代码片段和文本的地方。要创建笔记本,请输入 /newNotebook,并为笔记本提供一个名称,例如,products

图 20.4:创建笔记本

上图显示了您输入 /newNotebook 命令时显示的内容;您需要通过点击 创建笔记本 来确认笔记本的框架。

这将创建一个包含 products 常用代码片段的新笔记本。接下来,让我们检查输出。

您的笔记本,从高层次来看,包含以下内容:

  • 导入,包含处理数据集所需的所有导入;内容如下所示:

    import pandas as pd # Import Pandas library
    import numpy as np # Import NumPy library
    import matplotlib.pyplot as plt # Import Matplotlib library for data visualization
    import seaborn as sns # Import Seaborn library for data visualization 
    
  • 加载数据,包含从 CSV 文件(CSV 文件 product_data.csv 不存在,但 Copilot 假设您有这样一个文件;调整此部分的代码以适应您拥有的 CSV 文件名称)加载数据的代码:

    # Load product data from CSV file into a Pandas DataFrame
    product_data = pd.read_csv('product_data.csv')
    # Load product data from JSON file into a Pandas DataFrame
    # product_data = pd.read_json('product_data.json')
    # Display the first 5 rows of the DataFrame
    print(product_data.head()) 
    
  • 探索产品数据,包含检查数据的操作:

    # Use Pandas functions to explore the product data
    # Display the last 5 rows of the DataFrame
    print(product_data.tail())
    # Display the summary information of the DataFrame
    print(product_data.info())
    # Display the statistical summary of the DataFrame
    print(product_data.describe()) 
    
  • 过滤产品数据,包含过滤数据的操作:

    # Filter product data based on specific criteria using boolean indexing
    filtered_data = product_data[(product_data['category'] == 'electronics') & (product_data['price'] < 100)]
    # Display the filtered data
    print(filtered_data) 
    
  • 数据排序,包含排序操作:

    # Sort the product data based on one or more columns using the sort_values() function
    # Sort by price in ascending order
    product_data.sort_values(by='price', ascending=True, inplace=True)
    # Sort by category in ascending order, then by price in descending order
    product_data.sort_values(by=['category', 'price'], ascending=[True, False], inplace=True)
    # Display the sorted data
    print(product_data) 
    
  • 数据聚合;通常,您希望聚合数据以更好地理解它:

    # Group the product data by category and calculate the total price for each category
     category_total_price = product_data.groupby('category')['price'].sum()
     # Display the total price for each category
     print(category_total_price)
     # Group the product data by category and calculate the average price for each category
     category_avg_price = product_data.groupby('category')['price'].mean()
     # Display the average price for each category
     print(category_avg_price)
     # Group the product data by category and calculate the number of products in each category
     category_product_count = product_data.groupby('category')['product_id'].count()
     # Display the number of products in each category
     print(category_product_count) 
    

总结来说,这些都是您不必自己编写的代码。您可以使用这个笔记本作为处理产品数据的起点。

创建项目

另一个非常有用的命令是能够创建项目。通常,我们要么手动创建文件,这需要时间,要么依赖 CLI 工具为我们创建项目。使用 Copilot,您可以在聊天窗口中输入 /new 来创建项目。要使用此命令,请输入 /new 并为其提供描述,例如,React。这将创建一个带有 React 项目模板的新项目。让我们检查输出:

`my-react-app`
`├── public`
`│   ├── index.html`
`│   └── favicon.ico`
`├── src`
`│   ├── App.js`
`│   ├── index.js`
`│   ├── components`
`│   │   ├── Customer.js`
`│   │   ├── Product.js`
`│   │   ├── CustomerList.js`
`│   │   └── ProductList.js`
`│   ├── data`
`│   │   ├── customers.json`
`│   │   └── products.json`
`│   └── styles`
`│       ├── App.css`
`│       ├── Customer.css`
`│       ├── Product.css`
`│       ├── CustomerList.css`
`│       └── ProductList.css`
`├── package.json`
`└── README.md` 

上文创建的文件是 React 项目中预期的典型文件。它包含:

  • public,其中包含index.html文件,该文件引用 JavaScript 文件,指示应用程序的位置。

  • src,它包含所有应用程序文件,并且自身被分为不同的文件夹,用于组件、数据和 CSS 样式。

所有这些构成了一个起点。你可能希望根据你的喜好更改文件。

调试和故障排除

能够调试和故障排除代码是一项重要的技能。当你开发代码时,它很少第一次就能做到你想要的效果。你需要能够调试和故障排除代码,以找出问题所在。如果你有一个报告的 bug,也是如此;你需要能够诊断它在哪个点出错。

Copilot 可以帮助,一个好的方法就是使用 Copilot 聊天,并要求它添加对调试和故障排除的支持。

以下是你被给出的一个示例代码。这是一个用于 Python 的 Flask 框架中的 REST API。这是一段可以工作的代码,但从调试和故障排除的角度来看可能并不容易使用:

from flask import Flask, jsonify, request
import sqlite3
app = Flask(__name__)
# Create a connection to the database
conn = sqlite3.connect('products.db')
c = conn.cursor()
# Create a table for products if it doesn't exist
c.execute('''CREATE TABLE IF NOT EXISTS products
             (id INTEGER PRIMARY KEY AUTOINCREMENT,
              name TEXT,
              price REAL)''')
conn.commit()
@app.route('/products', methods=['GET'])
def get_products():
    # Retrieve all products from the database
    c.execute('SELECT * FROM products')
    products = c.fetchall()
    # Convert the products to a JSON response
    response = []
    for product in products:
        response.append({
            'id': product[0],
            'name': product[1],
            'price': product[2]
        })
    return jsonify(response)
@app.route('/products', methods=['POST'])
def add_product():
    # Retrieve the product data from the request body
    data = request.get_json()
    name = data['name']
    price = data['price']
    # Insert the product into the database
    c.execute('INSERT INTO products (name, price) VALUES (?, ?)', (name, price))
    conn.commit()
    # Return a success message
    return jsonify({'message': 'Product added successfully'})
if __name__ == '__main__':
    app.run(debug=True) 

上述代码可以工作,但它不支持调试和故障排除;让我们看看如何通过使用 Copilot Chat 来添加对该功能的支持:

[提示]

添加对故障排除和调试的支持

[提示结束]

Copilot 聊天以文本和代码的形式回答我们:

[提示响应]

当然,这是一个包含故障排除和调试支持的 Flask API 更新版本:

from flask import Flask, jsonify, request
import sqlite3
app = Flask(__name__)
# Create a connection to the database
conn = sqlite3.connect('products.db')
c = conn.cursor()
# Create a table for products if it doesn't exist
c.execute('''CREATE TABLE IF NOT EXISTS products
             (id INTEGER PRIMARY KEY AUTOINCREMENT,
              name TEXT,
              price REAL)''')
conn.commit()
@app.route('/products', methods=['GET'])
def get_products():
    try:
        # Retrieve all products from the database
        c.execute('SELECT * FROM products')
        products = c.fetchall()
        # Convert the products to a JSON response
        response = []
        for product in products:
            response.append({
                'id': product[0],
                'name': product[1],
                'price': product[2]
            })
        return jsonify(response)
    except Exception as e:
        # Log the error and return an error message
        app.logger.error(str(e))
        return jsonify({'error': 'An error occurred while retrieving products'})
@app.route('/products', methods=['POST'])
def add_product():
    try:
        # Retrieve the product data from the request body
        data = request.get_json()
        name = data['name']
        price = data['price']
        # Insert the product into the database
        c.execute('INSERT INTO products (name, price) VALUES (?, ?)', (name, price))
        conn.commit()
        # Return a success message
        return jsonify({'message': 'Product added successfully'})
    except Exception as e:
        # Log the error and return an error message
        app.logger.error(str(e))
        return jsonify({'error': 'An error occurred while adding the product'})
if __name__ == '__main__':
    app.run(debug=True) 

改进如下:

  • get_products()函数中的代码周围添加了try/except块。

  • 添加了logger语句来记录错误。

  • app.run()语句中添加了debug=True

这些更改将帮助我们进行故障排除和调试代码。

[响应结束]

让我们看看如何使用 Copilot 帮助我们进行代码审查和优化。

代码审查和优化技术

目前,我们有一段可以工作的代码,但它还没有优化。例如,通常将代码分离到不同的文件中是一个好主意。让我们看看 Copilot 建议如何改进代码。

[提示]

改进选定的代码

[提示结束]

如同往常,我们以文本和代码的形式获取 Copilot 聊天响应:

[提示响应]

当然,以下是对所选代码的一些改进建议:

  • 使用上下文管理器来处理数据库连接和游标。这确保了当代码执行完毕时,连接被正确关闭,即使发生异常也是如此。

  • 使用列表推导式将产品转换为 JSON 响应。这比使用 for 循环更简洁,可能更快。

  • 使用 Response 对象的 json 参数返回 JSON 响应。这比使用 jsonify 函数更简洁。

  • 使用 request.json 属性从请求体中检索产品数据。这是一种更简洁、更 Pythonic 的方法。

以及生成的代码:

from flask import Flask, Response, request
import sqlite3
app = Flask(__name__)
# Create a table for products if it doesn't exist
with sqlite3.connect('products.db') as conn:
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS products
                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
                  name TEXT,
                  price REAL)''')
    conn.commit()
@app.route('/products', methods=['GET'])
def get_products():
    try:
        # Retrieve all products from the database
        with sqlite3.connect('products.db') as conn:
            conn.row_factory = sqlite3.Row
            c = conn.cursor()
            c.execute('SELECT * FROM products')
            products = c.fetchall()
        # Convert the products to a JSON response
        response = [{'id': product['id'], 'name': product['name'], 'price': product['price']} for product in products]
        return Response(response=response, status=200, mimetype='application/json')
    except Exception as e:
        # Log the error and return an error message
        app.logger.error(str(e))
        return Response(response={'error': 'An error occurred while retrieving products'}, status=500, mimetype='application/json')
@app.route('/products', methods=['POST'])
def add_product():
    try:
        # Retrieve the product data from the request body
        data = request.json
        name = data['name']
        price = data['price']
        # Insert the product into the database
        with sqlite3.connect('products.db') as conn:
            c = conn.cursor()
            c.execute('INSERT INTO products (name, price) VALUES (?, ?)', (name, price))
            conn.commit()
        # Return a success message
        return Response(response={'message': 'Product added successfully'}, status=201, mimetype='application/json')
    except Exception as e:
        # Log the error and return an error message
        app.logger.error(str(e))
        return Response(response={'error': 'An error occurred while adding the product'}, status=500, mimetype='application/json')
if __name__ == '__main__':
    app.run(debug=True) 

[响应结束]

上述最大的变化是使用上下文管理器来处理数据库连接和游标。这确保了当代码执行完毕时,连接会被正确关闭,即使发生异常也是如此。

我们仍然希望将代码分离到不同的文件中;让我们看看 Copilot 对此有什么建议。

让我们从以下提示开始:

[提示]

根据职责将代码分离到不同的文件中

[结束提示]

以及生成的代码:

[提示响应]

# app.py
from flask import Flask
from products import products_bp
app = Flask(__name__)
app.register_blueprint(products_bp)
if __name__ == '__main__':
    app.run(debug=True)
# database.py
import sqlite3
def get_db():
    db = sqlite3.connect('products.db')
    db.row_factory = sqlite3.Row
    return db
# products.py
from flask import Blueprint, jsonify, request
from database import get_db
products_bp = Blueprint('products', __name__)
@products_bp.route('/products', methods=['GET'])
def get_products():
    try:
        # Retrieve all products from the database
        db = get_db()
        products = db.execute('SELECT * FROM products').fetchall()
        # Convert the products to a JSON response
        response = [{'id': product['id'], 'name': product['name'], 'price': product['price']} for product in products]
        return jsonify(response)
    except Exception as e:
        # Log the error and return an error message
        app.logger.error(str(e))
        return jsonify({'error': 'An error occurred while retrieving products'}), 500
@products_bp.route('/products', methods=['POST'])
def add_product():
    try:
        # Retrieve the product data from the request body
        data = request.json.get('data', {})
        name = data.get('name', '')
        price = data.get('price', 0)
        # Insert the product into the database
        db = get_db()
        db.execute('INSERT INTO products (name, price) VALUES (?, ?)', (name, price))
        db.commit()
        # Return a success message
        return jsonify({'message': 'Product added successfully'}), 201
    except Exception as e:
        # Log the error and return an error message
        app.logger.error(str(e))
        return jsonify({'error': 'An error occurred while adding the product'}), 500 

[结束响应]

如果在运行代码时收到任何错误,请咨询 Copilot,告诉它有错误,并要求它解决问题。请记住,Copilot 不仅仅是一个代码生成器。

我们现在看到的是关注点的分离。app.py 文件负责启动应用程序并注册蓝图。database.py 文件负责处理数据库连接。products.py 文件负责处理 products API。这段代码是我们可以操作和维护的。

通过将 database.py 更改为使用上下文管理器,可以进一步改进此代码:

import sqlite3
def get_db():
    with sqlite3.connect('products.db') as db:
        db.row_factory = sqlite3.Row
        return db
def init_db():
    with sqlite3.connect('products.db') as db:
        db.execute('''CREATE TABLE IF NOT EXISTS products
                     (id INTEGER PRIMARY KEY AUTOINCREMENT,
                      name TEXT,
                      price REAL)''')
        db.commit()
# other code omitted for brevity 

在这里,您看到了 Copilot 如何逐步审查您的代码,将单个文件拆分为多个文件,然后改进代码。

工作空间

您的工作空间是您在目录中打开的所有文件。您希望您的 AI 助手在提供推荐时考虑所有这些文件。让我们看看以下存在的以下文件的网络项目:

`src/`
 `app.py`
 `utils.py` 

app.py 包含一个 API,而 utils.py 包含一个电子邮件验证函数。以下是 app.py 的代码:

# create a web api using Flask, should have a products GET route
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/customers, methods=['GET'])
def get_customers():
    return jsonify({'customers': ['item1', 'item2', 'item3']})
# TODO, create a POST route for products
# Run the app
if __name__ == '__main__':
    app.run(debug=True) 

注意到 TODO 注释;我们即将添加一个存储客户的路由。在存储此客户时,我们需要确保客户数据包含一个有效的电子邮件。幸运的是,utils.py 文件已经包含这样一个函数,但我们能否让 Copilot 使用它?让我们看看。

app.py 中的注释更改为以下内容:

[提示]

TODO,为客户创建一个 POST 路由。它应该验证请求体,并确保它有一个有效的电子邮件地址。

[结束提示]

你得到一个基本函数,它实际上并没有做它应该做的事情:

[提示响应]

@app.route('/customers', methods=['POST'])
def post_customers():
    return jsonify({'message': 'success'}) 

[结束响应]

为了帮助解决这个问题,您求助于 GitHub Chat 并输入以下提示:

[提示]

验证请求体并确保它有一个有效的电子邮件地址

[结束提示]

[提示响应]

要验证请求体并确保它有一个有效的电子邮件地址,您可以使用 Flask 的 request 对象和一个正则表达式。以下是您如何修改您的 post_customers 函数的方法:

from flask import request
import re

@app.route('/customers', methods=['POST'])
def post_customers():
    data = request.get_json()

    if 'email' not in data:
        return jsonify({'message': 'No email provided'}), 400

    email = data['email']
    pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'

    if not re.match(pattern, email):
        return jsonify({'message': 'Invalid email format'}), 400

    return jsonify({'message': 'success'}) 

[结束响应]

虽然上述代码可以工作,但它没有利用我们现有的 utils.py 文件,其中已经有一个函数可以解决验证电子邮件的问题。那么我们如何让 Copilot 考虑该文件呢?答案在于一个名为 @workspace 的命令。@workspace 命令考虑所有文件。以下是它的用法:

[提示]

@workspace 改进选定的代码以验证请求体,确保它包含有效的电子邮件地址,主要使用工作区中其他文件的功能来解决问题。

[提示结束]

图 20.5:Copilot Chat 中的图像,展示了如何使用 @workspace 命令利用工作区中现有的代码和代码文件来解决问题,而不是创建新的代码。

让我们也在代码中展示响应并讨论我们可以看到的内容:

[提示响应]

from flask import request
from utils import validate_email

@app.route('/customers', methods=['POST'])
def post_customers():
    data = request.get_json()

    if 'email' not in data:
        return jsonify({'message': 'No email provided'}), 400

    email = data['email']

    if not validate_email(email):
        return jsonify({'message': 'Invalid email format'}), 400

    return jsonify({'message': 'success'}) 

[响应结束]

在上面的代码中,validate_email 函数位于 utils.py 文件中,用于验证电子邮件。这对于第一次尝试来说很重要,解决这个问题导致了 Copilot 重复代码。因此,当你想让 Copilot 考虑工作区中的所有代码来建议解决方案时,建议使用 @workspace

Visual Studio Code 查找

Copilot 的另一个强大功能是能够快速查找在 Visual Studio Code 中如何做某事。与其查阅官方文档,你可能会希望快速找到所需内容而不离开代码编辑器。要使用此功能,有一个 @vscode 命令。让我们确保聊天窗口已打开,并查看以下提示如何工作:

[提示]

@vscode 如何打开终端窗口?

[提示结束]

图 20.6:对上述提示的 Copilot 响应

这里有两点值得注意:

  • Copilot 告诉你如何做某事以及在哪里找到它。

  • 如果你点击 在命令面板中显示 按钮,它会提供视觉上展示如何找到所需内容的选项。

终端

我们将要向您展示的最后一个真正出色的功能是终端命令。理论上,您可以在不使用此特定命令的情况下使用终端,但如果您不必在问题前加上“在终端中如何做某事”,这会很有帮助。要使用此命令,请在提示前加上 @terminal,如下所示:

[提示]

@terminal 在 Git 中创建一个名为 upstream 的远程,它指向远程仓库。

[提示结束]

[提示响应]

要创建一个名为“upstream”的远程,它指向 Git 中的远程仓库,你可以使用 git remote add 命令。以下是命令:

  • git remote add upstream {remote_repo_url}

  • {remote_repo_url} 替换为远程仓库的 URL。

  • 此命令添加了一个名为“upstream”的新远程,它指向指定的远程仓库。然后您可以根据需要从“upstream”远程获取或推送。

  • {remote_repo_url}:远程仓库的 URL。这可能是一个 GitHub、GitLab、Bitbucket 或任何其他 Git 服务器上的 URL。

[响应结束]

如您所见,您既得到了所需的命令,又得到了如何输入的详细说明。

作业

尝试通过添加文档、测试或可能是一个 ORM(对象关系映射器)来进一步改进代码。请记住使用本章中涵盖的命令。

挑战

查看 Copilot Chat 支持的其他命令,看看你是否可以用它们来改进你的工作流程。

问答

Copilot 能帮助什么?

  1. 代码生成、自动化、调试、故障排除、代码审查和优化

  2. 部署

  3. 以上皆非

摘要

在本章中,我们介绍了 Copilot 中一些更高级的功能。你学习了如何使用@workspace来让 Copilot 考虑所有你的文件。@vscode命令也是那个功能中的一个有用命令,它展示了如何与 Visual Studio Code 一起工作。

我们还探讨了脚手架的使用——特别是如何为 Web 项目搭建脚手架——以及如何创建带有起始代码的笔记本。这些命令在你刚开始一个项目时可能会为你节省数小时。Copilot 有很多命令,我建议你尝试一下。

加入我们的 Discord 社区

加入我们的 Discord 空间,与作者和其他读者进行讨论:

packt.link/aicode

第二十一章:软件开发中的代理

引言

本章将向您介绍软件开发中代理的概念。我们将涵盖代理是什么,它们如何工作,以及如何在项目中使用它们。我们还将介绍一些最受欢迎的代理框架以及如何开始使用它们。

让我们介绍代理可以解决的问题。一般想法是有一个可以代表你行动的程序。这类程序的例子可以是自动化任务、做出决策以及与其他代理和人类交互。这样的程序可以为你节省时间,使你的生活更轻松或使你的业务更高效。

在本章中,我们将做以下事情:

  • 介绍软件开发中代理的概念。

  • 解释代理是什么以及它们是如何工作的。

  • 讨论不同类型的代理以及它们如何被使用。

代理是什么?

如前所述,代理是代表你行动的程序。它们可以执行任务、做出决策以及与其他代理和人类交互。代理可以用在广泛的领域中。

几个因素使一个程序成为代理程序而不是普通程序:

  • 代理程序 有一个明确的目标:例如,考虑一个保持 25 度温度并采取适当行动以保持该温度的恒温器,或者一个管理财务并试图最大化利润的代理。

  • 自主性:代理做出必要的决策以确保它达到之前定义的目标。对于金融代理来说,这可能意味着在满足特定触发条件时买卖股票。

  • 具有传感器:传感器可以是物理的,也可以是软件中的 API,它使代理能够理解“世界是什么样的”。对于恒温器来说,传感器是温度指示器,但对于金融代理来说,传感器可以是股票市场的 API,使代理能够决定他们的目标。

代理是如何工作的?

代理通过接收输入、处理输入并产生输出来工作。它们可以被编程来执行特定任务、做出决策以及与其他代理和人类交互。代理还可以从它们的交互中学习并随着时间的推移提高它们的性能。

图 21.1:简单代理的过程:关键字,识别,执行任务

简单代理与使用 AI 的代理

代理并不是什么新鲜事物。它们已经存在很长时间了。新的地方在于代理现在正被 AI 所驱动。让我们比较一下两者:

  • 简单代理:传统代理被编程来执行特定任务,并基于预定义的规则和逻辑做出决策。

  • 使用 AI 的代理:由 AI 驱动的代理能够执行更复杂的任务并做出更智能的决策。它们能够理解自然语言,从它们的交互中学习,并随着时间的推移提高它们的性能。

简单代理

如前几节所述,简单的代理在以下方面有限:它们是为特定任务制作的。与他们互动通常也是有限的——你只能使用关键词,或者你表达自己的方式是有限的。

一个简单的代理的例子是聊天机器人。这样的聊天机器人被编程为理解有限的关键词和短语。

例如,“告诉我更多关于你们产品的事情,”或者“你们的退换货政策是什么?”任何超出这些关键词和短语的对话尝试都将导致聊天机器人无法理解用户。

一个简单的代理不是一个好的对话者

当你与人类交谈时,你期望他们精通几个主题,并且至少能够谈论其他主题。对于一个简单的代理,我们可能会陷入以下对话:

  • 用户:“告诉我你们的产品。”

  • 代理:“我们有很多产品,包括电子产品、服装和配件。请告诉我你的兴趣。”

  • 用户:“我对衣服感兴趣,想找一些适合当前天气的。”

  • 代理:“我当然可以提供关于服装的建议,但我不知道当前天气。”

在这里,我们可以观察到两个有趣的现象:

  • 对话感觉很短,重要的信息要么在句子的末尾,要么就在逗号之前,这表明使用了简单的解析来提取重要信息。

  • 它无法处理非产品信息,如天气,这有助于筛选出有用的响应。

使用工具调用和大型语言模型(LLM)改进对话

LLM 是一个改进,因为它擅长听起来更自然,而且它可以从流畅的文本中解析和识别意图。你也可以通过所谓的工具调用向 LLM 提供额外的知识,其中你告诉 LLM 关于各种能力的信息,如衣服 API、天气 API 等,这样它可以更好地处理对话,并类似于人类的对话。让我们用同一个 LLM 对话并突出其差异:

  • 用户:“你们有哪些产品?”

  • 代理:“我们有很多产品,包括电子产品、服装和配件。”

  • 用户:“太好了,我对衣服感兴趣,想找一些适合当前天气的。你有什么推荐吗?”

  • 代理:“你能告诉我你的位置,这样我就能更好地为你提供服装建议吗?”

  • 用户:“当然,我住在亚利桑那州的凤凰城。”

  • 代理:“我现在看到凤凰城现在是 90 华氏度。我可以建议这些短裤吗?”

这次对话之所以表现更好,是因为这个 LLM 得益于工具调用,该工具使用凤凰作为输入调用其天气 API,然后继续调用衣服 API,以天气响应作为过滤器。

对话代理的解剖结构

一个对话代理通常由以下组件组成:

  • 输入:代理的输入,通常是自然语言的形式。应该指出的是,这可以是非常多的不同 spoken languages,而不仅仅是英语,你过去不得不将其硬编码。

  • 处理:输入的处理,通常使用自然语言处理(NLP)技术。

  • 委托:将输入委托给代理的适当组件。它委托到的组件可以是特定任务的代理,例如预订航班或回答问题。

图 21.2:对话代理处理步骤

前面的图示表明了一个循环,你从输入到处理再到委托到结果,那么为什么会有循环呢?代理没有结束的概念;它在那里等待用户提供输入,并对它做出反应。如本章前面所述,代理致力于实现目标,如果目标是管理财务,那么它是一项持续的工作。

关于 LLM 中工具调用的更多内容

我们在本章中之前提到了工具调用,但让我们尝试展示如何向 LLM 添加功能。

LLM 只知道它被训练的内容,对于它没有训练的内容,在许多情况下,它将尝试提供一个答案,这个答案并不总是正确的,因为它是在编造;这被称为幻觉。为了提高你希望 LLM 提供更准确响应的领域,你可以向它提供一个工具。提供工具的过程包括以下组件:

  • 函数的 JSON 描述

  • 函数的描述,这样 LLM 就知道何时调用此函数

一旦你提供了前面的组件,比如说你提供了一个能够获取天气的函数;LLM 现在可以使用其内置功能来语义解释所有后续的输入,意味着用户想要了解天气:

  • “今天盐湖城的天气怎么样?”

  • “旧金山的温度是多少?”

  • “明天纽约会下雨吗?”

  • “伦敦的天气怎么样?”

  • “外面暖和吗?”

使用工具增强 GPT 的功能

它的工作原理是,你以 JSON 格式提供一个函数规范。这个 JSON 函数格式是 GPT 模型理解的模式。GPT 模型将基本上为你做两件事:

  • 从提示中提取参数。

  • 确定是否调用函数以及调用哪个函数,因为你可以告诉它关于多个函数的信息。

作为开发者,如果你认为 LLM 应该调用该函数,那么你需要主动调用该函数。

你的函数格式遵循以下模式:

{
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "Get the current weather",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, CA",
                    },
                    "format": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "The temperature unit to use. Infer this from the users location.",
                    },
                },
                "required": ["location", "format"],
            },
        }
    } 

在前面的 JSON 模式中,有一些事情是你告诉 GPT 模型的:

  • 有一个名为 get_current_weather 的函数。

  • 描述是 "获取当前天气"

  • 函数接受两个参数,locationformat

  • 同时还有对参数、它们的类型和允许的值的描述。

让我们描述一下在以下提示下,这将在实践中是如何工作的:

[提示]

“今天盐湖城的天气怎么样?”

[提示结束]

这是 GPT 模型可以从提示中提取的内容:

  • 位置:盐湖城。

  • 格式:此信息未提供,但 GPT 可以从用户的地理位置推断出来。

  • 函数调用:get_current_weather

作为开发者,您需要做的是调用提取的参数值指示的函数。以下是可以用来连接 GPT 模型并解析响应的代码,其中提供了函数描述:

import open
def get_current_weather(location, format):
    # Call weather API
    response = requests.get(f"https://api.weather.com/v3/wx/forecast/daily/5day?location={location}&format={format}
    return response.json()
# Call the GPT model
tool = {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "Get the current weather",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, 
CA",
                    },
                    "format": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "The temperature unit to use. Infer this from the users location.",
                    },
                },
                "required": ["location", "format"],
            },
        }
    }
prompt = "What's the weather like today in Salt Lake City?"
response = openai.Completion.create(
  model="text-davinci-003",
  prompt=prompt,
  max_tokens=150,
  tools= [tool]
)
# Parse the response
function_response = response.choices[0].function_response # here we learn what function to call
location = function_response.parameters.location # extracting parameter value for location
format = function_response.parameters.format # extracting parameter value for format
weather = get_current_weather(location, format) # here we get the response from the API 

这可能是如何使用 GPT 创建对话代理的最基本示例。

然而,我们希望高级代理能做得更多。如果我们想做一些更复杂的事情,比如预订航班、酒店、车辆和餐厅呢?

高级对话

很容易认为您只需向 GPT 模型添加更多函数,这可能会暂时有效。然而,随着对话变得更加复杂,GPT 模型需要记住对话的上下文并跟踪对话状态。

对于更高级的对话,这会迅速变得更加复杂。

想象一下走进旅行社,两个人之间发生的以下对话:

用户:“我想去度假。”

代理:“当然。”

用户:“我想去一个温暖的地方。”

代理:“当然,告诉我您想找什么。”

用户:“我想要一个有海滩的地方。”

代理:“当然,还有其他细节吗?”

用户:“我希望温度至少有 25 度。”

代理:“当然,还有其他细节吗?”

用户:“不,就这样。”

代理:“我找到了三个符合您标准的地方。我可以向您展示它们吗?”

用户:“是的,请。”

代理:“这里有三个地方。您想了解哪一个的更多信息?”

用户:“我想第一个。”

代理:“这里有第一个地方的信息。”

用户:“好的,你能为第一个地方预订酒店和航班吗?”

代理:“当然,我会为您处理这件事。”

代理:“我已经为您预订了酒店和航班。”

用户:“谢谢,哦,我还需要租一辆车。”

代理:“当然,您有特别想要的车型吗?”

用户:“不是真的;我的预算是每天 100 美元。”

代理:“我找到了五辆车符合您的标准。我可以向您展示它们吗?”

用户:“是的,请。我想订第五个。”

代理:“我已经为您预订了车辆。”

代理:“您的假期一切准备就绪。”

用户:“谢谢,但等等,我需要帮助预订第一晚的餐厅。”

到目前为止,很明显这次对话可能会持续相当长一段时间。代理需要记住对话的上下文并跟踪对话状态。

代理还需要将许多不同的任务委派给其他代理或服务,如预订酒店、航班、车辆和餐厅,以及天气 API、观光 API 等。

重要的是,代理不仅仅是理解初始提示并将任务委托给另一个代理或服务。您需要将这次对话视为一个状态机以及不同代理和服务的编排。

高级对话建模

我们提到更高级的对话涉及记住上下文和状态。让我们检查示例对话的一个子集,看看状态是如何变化的:

用户:“我想去度假。”

代理:“当然。”

在这一点上,代理除了记住用户的意图,即去度假,没有记住其他任何东西。它只是确认了用户的提示。

用户:“我想去一个温暖的地方。”

代理:“当然,告诉我您想找什么。”

现在事情变得有趣了。代理已经将“温暖”作为标准记住,并需要将其转换为可以用来过滤掉太冷的地方的温度范围。

用户:“我想找一个有海滩的地方。”

代理:“当然,还有其他细节吗?”

这又是一个进步;代理已经将“海滩”作为过滤地点时使用的额外标准记住。

用户:“我想至少 25 度。”

添加了一个额外的标准,“25 度”。让我们看看之前的标准,“温暖”,它被定义为 20-40 摄氏度——这调整了范围为 25-40 摄氏度。

代理:“当然,还有其他细节吗?”

用户:“不,就这样。”

在这一点上,代理意识到用户没有更多的标准要添加,并且可以基于“温暖”、“海滩”和“25-40 摄氏度”的过滤器进行搜索/决策。现在,调用 API 以获取地点列表,代理可以将列表展示给用户进行选择。

代理:“我找到了三个符合您标准的地方。我可以向您展示它们吗?”

需要补充的是,不仅需要记住这次特定旅行检索的标准,还需要记住后续步骤的标准,除非用户更改标准。

希望您可以从前面的例子中看出,状态是逐渐建立起来的,代理需要记住对话的上下文。

将更高级的对话视为以下步骤可能是有帮助的:

  1. 输入:代理的输入,通常是自然语言的形式。

  2. 处理:对输入的处理,通常使用 NLP 技术。

  3. 确定下一步:代理需要根据输入和对话的当前状态确定对话的下一步。这里的答案可以是要求更多信息、展示选项列表、预订某物等等。

  4. 结束对话或继续(请求用户输入):代理需要确定对话是否应该结束或继续。如果应该继续,它需要请求用户输入。

高级对话的伪代码

代理可能有几个不同的状态,例如:

  • 请求任务:这通常在对话开始时或完成任务并完成用户选择时提出。

  • 向用户请求更多关于任务的信息:这通常在执行任务之前提出,以确保代理拥有所有所需的信息。

  • 向用户展示选项列表:这通常在完成任务后提出,以向用户提供选择。

  • 执行任务:在这里,代理会执行任务,比如预订酒店、航班、汽车或餐厅。

  • 结束对话:当对话结束时,并且用户以某种方式表明对话结束时,代理会移动到这个状态。

这可能是伪代码的样子:

# enum
class State(Enum):
    ASK_FOR_TASK = 1
    ASK_FOR_MORE_INFORMATION = 2
    PRESENT_TASK_RESULT = 3
    PERFORM_TASK = 4
    END_CONVERSATION = 5
# initial state
state = State.ASK_FOR_TASK
def ask_for_task():
    # ask the user for a task
    pass
def ask_for_more_information(task):
    # store filter criteria
    pass
def present_task_result(task):
    # presents the result so the user can choose
    pass
def perform_task(task):
    # Perform a task
    pass
def end_conversation():
    # End the conversation
    pass
while state != State.END_CONVERSATION:
    if state == State.ASK_FOR_TASK:
        # Ask for a task
        task = ask_for_task()
        state = State.ASK_FOR_MORE_INFORMATION
    elif state == State.ASK_FOR_MORE_INFORMATION:
        # Ask the user for more information on a task
        task = ask_for_more_information(task)
        state = State.PERFORM_TASK
    elif state == State.PRESENT_TASK_RESULT:
        # Present a list of options to the user
        task = present_task_result(task)
        state = State.ASK_FOR_MORE_INFORMATION
    elif state == State.PERFORM_TASK:
        # Perform a task
        perform_task(task)
        state = State.PRESENT_TASK_RESULT
    elif state == State.END_CONVERSATION:
        # End the conversation
        end_conversation() 

上述代码是一个更高级对话的不错起点。然而,我们应该记住,人类并不总是可预测的,代理需要能够处理意外情况。例如,人类可以在任何时候改变主意或添加新的标准。

自主代理

自主代理是指可以在没有人类干预的情况下采取行动的代理。它们可以执行任务、做出决策,并与其他代理和人类进行交互,而不需要人类输入。自主代理可以用于广泛的领域,从自动驾驶汽车到虚拟助手。

自主代理的例子包括以下内容:

  • 自动驾驶汽车:自动驾驶汽车是无需人类干预即可自行驾驶的自主代理。它们可以导航道路、避开障碍物,并根据周围环境做出决策。

  • 虚拟助手:虚拟助手是无需人类输入即可执行任务、做出决策并与人类交互的自主代理。它们可以理解自然语言,从交互中学习,并随着时间的推移提高性能。想象一下这样一个例子,你有一个财务代理;你可能给它设定了一个高级目标,即管理你的财务并确保每年价值增长 8%。你可能允许这个代理在特定价值以下买卖股票,但对于超过一定限额的更大购买和销售,则需要咨询你。

未来我们可能会看到对话代理和自主代理数量的增加。这些代理可以在许多有趣的领域得到应用,从客户服务到医疗保健、金融和交通。想象一下,有一个自主代理可以帮助你处理税务、预订假期或在你了解你的医疗历史、偏好等信息的情况下帮助你照顾健康。

作业

给定以下对话,推断可能涉及哪种类型的代理以及你会如何建模对话:

用户:“我想烤一个蛋糕。”

代理:“当然,你想要做什么类型的蛋糕?”

用户:“我想做一个巧克力蛋糕。”

代理:“当然,你有没有想到具体的食谱?”

用户: “不,你能帮我找一个食谱吗?”

代理: “当然,关于过敏或偏好的方面,我应该知道些什么吗?”

用户: “我对坚果过敏,我更喜欢容易制作的食谱。哦,我还想做一个纯素的蛋糕。”

代理: “明白了!我找到了符合你标准的三个食谱。我可以展示它们吗?”

用户: “是的,请。”

代理: “这里有三个食谱。你想要哪一个?”

用户: “我要第二个。”

代理: “就这些吗?”

用户: “不,我需要购买原料吗?你能帮我设置烤箱温度吗?”

代理: “是的,看起来你缺少一些原料。我可以帮你订购原料。我还可以帮你设置烤箱温度。你需要什么时候设置?”

用户: “是的,下单,并在订单到达后立即设置烤箱。”

代理: “我已经下了订单,正在等待送货。我会在它到达时设置烤箱温度。”

用户: “谢谢。”

代理: “不客气,就这样吗?”

用户: “是的。”

挑战

你能想到一个可以用代理解决的问题吗?你会如何使用代理来解决这个问题?你会使用哪种类型的代理?

问答

Q:在 LLM 中工具调用是什么意思?

  1. 这是在 LLM 调用内置工具以提供响应时发生的。

  2. 这是在你通过提供 JSON 格式的函数描述和指示何时调用此函数的语义描述来让 LLM 知道一个新功能时发生的。

  3. 这是在你使用函数来微调 LLM 时发生的。

摘要

在本章中,我们介绍了软件开发中代理的概念。我们解释了代理是什么以及它们是如何工作的。我们讨论了不同类型的代理以及它们如何被使用。我们希望你现在能一窥未来,看看像 GPT 这样的 LLM 将走向何方,以及你的未来将如何改变。

参考文献

如果你对此类代理感到好奇,值得查看一些相关资源:

加入我们的 Discord 社区

加入我们社区的 Discord 空间,与作者和其他读者进行讨论:

packt.link/aicode

第二十二章:结论

感谢您,亲爱的读者,阅读到这本书的这一部分。我们希望您现在能够自信地在使用 GitHub Copilot 和 ChatGPT 等 AI 工具的项目中。

书籍回顾

让我们回顾一下这本书中我们涵盖的内容。我们首先向您介绍了 AI 的世界以及我们如何到达大型语言模型LLMs)。然后,我们向您介绍了 GitHub Copilot 和 ChatGPT,这两者是目前世界上最受欢迎的 AI 工具之一。这两个工具不仅受欢迎,而且值得比较和对比。ChatGPT 带有聊天界面,设计用来处理各种任务。GitHub Copilot 也带有聊天界面和编辑器模式,但更专注于解决软件开发中的问题。这两个工具的一个重要共同点是,您可以使用提示和自然语言输入,它们可以作为输入使用,并且最终用户会收到一个输出,希望这能帮助他们更接近解决问题。因为这两个工具都依赖于提示输入,这使我们能够决定需要什么类型的提示和多少提示,调整提示,甚至使用提示来验证 AI 工具的响应。

然后,我们向您展示了一种提示策略,以确保您有效地使用这些 AI 工具。之后,我们展示了这种提示策略如何在跨越前端到后端、涵盖几个章节的 Web 开发项目中得到应用。然后,我们展示了同样的提示策略如何应用于数据科学和机器学习项目。这两个问题领域是随机选择的;这本书的目的是展示生成式 AI 工具的能力,并且知道如何将它们与提示策略相结合将极大地增强您的能力。

使用 AI 工具的影响是,您现在拥有一个工具,它可以处理代码开发中的大部分繁重工作,无论问题领域如何。这对您作为开发者的意义在于,您现在可以更专注于声明性,即表达您想要的事情,而不是逐行编写代码。使用 AI 工具并高效地使用它们可能会显著加快您工作中编码的部分。

在总结这本书之前,我们向您展示了对未来的展望,即代理,这些是代表您行事并代表我们认为 AI 将走向何方的程序。

最后,我们更详细地介绍了 AI 工具及其功能,以便您在未来的项目中取得成功。

主要结论

那么,我们能从这本书中得出哪些主要结论呢?它们如下所示:

  • 类似于 GitHub Copilot 和 ChatGPT 这样的 AI 工具将长期存在,并且随着时间的推移只会变得更好。

  • 世界正在向更声明性的编程方式转变;让 AI 为您做大部分繁重的工作。您的新角色是与 AI 工具迭代合作,以获得最佳结果。

  • 无论你是网页开发者、数据科学家还是机器学习工程师,你都应该制定一个提示策略来处理你选择的领域内的问题。

  • 提示(Prompting)正迅速成为一项独立技能,即使我们认为改进后的模型将来会使其变得不那么重要。目前,这是一项你应该掌握的技能,我们希望这本书能帮助你在这条成为熟练提示师的道路上。

接下来是什么

我们在第二十一章中讨论的智能代理(Agents)是事情发展的方向,你应该密切关注这一点。智能代理已经是牛津大学的课程内容之一;查看这个课程链接了解更多信息 conted.ox.ac.uk/courses/artificial-intelligence-generative-ai-cloud-and-mlops-online

如第二十一章所述,Autogen 是一个非常有趣的项目,它引入了智能代理。我们建议您查看它,并在您自己的项目中如果愿意的话加以利用:github.com/microsoft/autogen

最后

人工智能的世界发展迅速。昨天的完美工具和策略可能在几个月后可能不再是最好的。因此,我们的目标是保持这本书更新,包含最新的发现和见解。衷心感谢您,亲爱的读者,我们希望这本书对您来说是有价值的。

加入我们的 Discord 社区

加入我们的 Discord 空间,与作者和其他读者进行讨论:

packt.link/aicode

二维码