大規模モデル開発 (14): OpenAI Chat モデル + Google API を使用して、インテリジェントな電子メール送受信のための AI アプリケーションを実装します。

全文は合計1.2wワード以上、予想読了時間は約24~40分 | 乾物(コード付き)も充実、コレクションするのがオススメ!

この記事の目標: Gmail API をチャット モデルに接続し、電子メールをインテリジェントに送受信する AI アプリケーションを作成する
ここに画像の説明を挿入

コードのダウンロードはここをクリックしてください

1. 背景

大規模なモデル アプリケーションの開発は、学習および AI アプリケーション開発のために Google Cloud から始まります。これは、敷居が最も低く、効率が最も高い方法ですGoogle Cloud は、さまざまなアプリケーション向けに数百の API を備えているだけでなく、統一された API システムと権限管理方法を備えており、呼び出しプロセスが非常に便利です。1 つのアカウントで 1 つのプロジェクトの認証を通じて多数の API を使用できるほか、詳細かつ完全な開発が可能です開発者向けドキュメントは、API を使用するための敷居を大幅に下げます。

同時に、Google Cloud はフル機能のアプリケーション開発プラットフォームでもあり、初期の探索のために API を使用するだけでなく、本当の意味でエンタープライズ レベルのアプリケーション開発を完了したい場合は、Google Cloud 上で行うこともできます。 。Google Cloud は、完全なオンライン アプリケーション開発およびリリース プロセスを提供するだけでなく、(比較的)安価で安定したクラウド サービスも提供します。開発者が開発したアプリケーションはクラウド上で直接実行でき、Google Cloud が提供する緊急時およびメンテナンスの完全なセットを利用できます。 . プロセスおよびリアルタイムの視覚監視ページ。

しかし、本当の問題は、中国ではまだアクセス制限があり、魔法を使う必要があるということです。この記事の目標は、Gmail API をチャット モデルに接続し、電子メールをインテリジェントに送受信する AI アプリケーションを作成することです。

2. Gmail APIのOAuth認証

まず最初のステップは、Google の API を呼び出すことです。これには承認が必要です。Google Cloud と Google Cloud API ライブラリの具体的な紹介と、Gmail API の OAuth 認証プロセスを完了する方法を理解している場合は、この手順をスキップして、次のコード内容を直接入力できます。 、以下のリンクを参照してください。

Gmail API の OAuth 認証プロセスを正常に実装することは非常に重要です。Google Cloud の他の API を使用して、より複雑な AI アプリケーションを構築しますが、そのほとんどには OAuth 認証が必要です。認証が完了している場合は、認証情報を次のアプリケーションに適用することもできます。その他 API 呼び出しの場合、認可は AI アプリケーション開発の前提条件でもあります。

AI アプリケーション開発: Gmail API が OAuth 認証を実装する方法

3. Gmail API を使用してインテリジェントなメール送受信アプリケーションを構築する

3.1 チャットモデルにメールチェック機能を追加

まず、Gmail API を Chat モデルに接続してみますこれら 3 つの記事では次のようになります。

大規模モデル開発(11):Chat Completionsモデルの関数呼び出し機能の詳細説明

大規模モデル開発(12):関数呼び出し処理の最適化と複数回の対話タスクの実現

大規模モデル開発 (13): 関数呼び出しは外部ツール API を呼び出し、リアルタイムの天気クエリを実現します

最適化された Function 呼び出し関数の実行プロセスが完了しました AI アプリケーションの開発では、基本機能を実現するという目標を決定した後、次の 4 つのステップに分かれます。

  1. 外部関数の機能が実現可能かどうかをテストする
  2. 大規模言語モデルに外部関数の結果を解釈し、外部関数のパラメータを正確に変換する機能があるかどうかを検証する
  3. これをもとに、外部ツールのAPIを呼び出せる外部関数を作成し、非常に詳細な関数の説明を書きます。
  4. 定義した外部関数を run_conversation 関数または chat_with_model 関数に組み込み、対話効果をテストします。

そこで、まずはChatモデルに外部機能を通じて最新メールを閲覧する機能を持たせてみます 具体的な実装手順は以下の通りです。

  • ステップ 1: 外部関数の実現可能性をテストする

まず、Gmail API を呼び出して、送信者、日付、メールの内容などの最新のメール情報を確認できるかどうかをテストします。コードは次のとおりです。

# 从本地文件中加载凭据
creds = Credentials.from_authorized_user_file('token.json')

# 创建 Gmail API 客户端
service = build('gmail', 'v1', credentials=creds)

# 列出用户的一封最新邮件
results = service.users().messages().list(userId='me', maxResults=1).execute()
messages = results.get('messages', [])

# 遍历邮件
for message in messages:
    # 获取邮件的详细信息
    msg = service.users().messages().get(userId='me', id=message['id']).execute()

    # 获取邮件头部信息
    headers = msg['payload']['headers']

    # 提取发件人、发件时间
    From, Date = "", ""
    for h in headers:
        name = h['name']
        if name.lower() == 'from':
            From = h['value']
        if name.lower() == 'date':
            Date = h['value']

    # 提取邮件正文
    if 'parts' in msg['payload']:
        part = msg['payload']['parts'][0]
        if part['mimeType'] == 'text/plain':
            data = part['body']["data"]
        else:
            data = msg['payload']['body']["data"]
    else:
        data = msg['payload']['body']["data"]
        
    data = data.replace("-","+").replace("_","/")
    decoded_data = base64.b64decode(data)
    str_text = str(decoded_data, "utf-8")
    msg_str = email.message_from_string(str_text)

    if msg_str.is_multipart():
        text = msg_str.get_payload()[0]  
    else:
        text = msg_str.get_payload()
    
    print('From: {}'.format(From[:8]))
    print('Date: {}'.format(Date))
    print('Content: {}'.format(text))

出力を見てください。

画像-20230727160514201

メールのステータスを確認します。

画像-20230727160611804

上記のコードの中心的なプロセスは、service.users().messages().list(userId='me', maxResults=1).execute() を通じて最新の電子メールの情報を取得し、最後にそれを msg に保持することです。 。

Gmail API によって返される結果の詳細については、Gmail API の公式 Web サイトの機能紹介を参照してください: https://developers.google.com/gmail/api/guides

  • ステップ 2: モデルが Gmail API によって返された結果を解釈できることを確認する

Gmail API が処理されない場合、返される結果は次のとおりです。

画像-20230727160830560

送信者情報、受信時刻、メールのテキスト情報など、さまざまな詳細情報が含まれます。さまざまな質問に対するより多くの回答を満たすために、チャット モデルにメッセージ結果を直接解釈させ、チャット モデルが Gmail API に精通しているかどうかをテストすることを検討してください。以下のプロセスを経て、関数の解釈とその戻り結果のコードは次のようになります。

import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.ChatCompletion.create(
  model="gpt-4-0613",
  messages=[
    {
    
    "role": "system", "content": "这是我的Gmail邮箱最近一封邮件的内容:%s" % msg},
    {
    
    "role": "system", "content": "邮件内容是由Gmail API获取"},
    {
    
    "role": "user", "content": "请问我的Gmail最近一封邮件是谁发送的,具体内容是什么?"}
  ]
)
response.choices[0].message['content']

出力を見てください。

画像-20230727161135323

Chat モデルは、msg に含まれる情報を非常によく理解できます。その後、外部関数と Chat モデルの間の通信プロセスを設計するときに、外部関数はメッセージ結果を大規模言語モデルに直接出力できます。

  • ステップ 3: 外部関数を作成する

ステップ 1 のコード フローに従って、最新の電子メール情報を返すことができる msg オブジェクトを作成し、それを JSON 形式で出力します。

def get_latest_email(userId):
    """
    查询Gmail邮箱中最后一封邮件信息
    :param userId: 必要参数,字符串类型,用于表示需要查询的邮箱ID,\
    注意,当查询我的邮箱时,userId需要输入'me';
    :return:包含最后一封邮件全部信息的对象,该对象由Gmail API创建得到
    """
    # 从本地文件中加载凭据
    creds = Credentials.from_authorized_user_file('token.json')
    
    # 创建 Gmail API 客户端
    service = build('gmail', 'v1', credentials=creds)
    
    # 列出用户的一封最新邮件
    results = service.users().messages().list(userId=userId, maxResults=1).execute()
    messages = results.get('messages', [])

    # 遍历邮件
    for message in messages:
        # 获取邮件的详细信息
        msg = service.users().messages().get(userId='me', id=message['id']).execute()
        
    return json.dumps(msg)
functions_list = [get_latest_email]

関数を作成した後、最終的なダイアログ関数がスムーズに実行できることを確認するために、まず auto_functions が関数の説明に従って関数パラメーターを正しく記述できるかどうかを手動でテストし、チャット モデルが関数の形式要件を正しく認識できるかどうかをテストします。パラメータを追加し、ユーザーのニーズをスムーズに解釈します。そして、関数が受け入れることができるパラメータ形式に変換します。まず、関数パラメータを導入して関数を自動的に記述します。

def auto_functions(functions_list):
    """
    Chat模型的functions参数编写函数
    :param functions_list: 包含一个或者多个函数对象的列表;
    :return:满足Chat模型functions参数要求的functions对象
    """
    def functions_generate(functions_list):
        # 创建空列表,用于保存每个函数的描述字典
        functions = []
        
        def chen_ming_algorithm(data):
            """
            陈明算法函数,该函数定义了一种特殊的数据集计算过程
            :param data: 必要参数,表示带入计算的数据表,用字符串进行表示
            :return:陈明函数计算后的结果,返回结果为表示为JSON格式的Dataframe类型对象
            """
            df_new = pd.read_json(data)
            res = np.sum(df_new, axis=1) - 1
            return res.to_json(orient='records')
        
        chen_ming_function_description = inspect.getdoc(chen_ming_algorithm)
        
        chen_ming_function_name = chen_ming_algorithm.__name__
        
        chen_ming_function = {
    
    "name": "chen_ming_algorithm",
                              "description": "用于执行陈明算法的函数,定义了一种特殊的数据集计算过程",
                              "parameters": {
    
    "type": "object",
                                             "properties": {
    
    "data": {
    
    "type": "string",
                                                                     "description": "执行陈明算法的数据集"},
                                                           },
                                             "required": ["data"],
                                            },
                             }

        
        # 对每个外部函数进行循环
        for function in functions_list:
            # 读取函数对象的函数说明
            function_description = inspect.getdoc(function)
            # 读取函数的函数名字符串
            function_name = function.__name__

            user_message1 = '以下是某函数说明:%s。' % chen_ming_function_description +\
                            '根据这个函数的函数说明,请帮我创建一个function对象,用于描述这个函数的基本情况。这个function对象是一个JSON格式的字典,\
                            这个字典有如下5点要求:\
                            1.字典总共有三个键值对;\
                            2.第一个键值对的Key是字符串name,value是该函数的名字:%s,也是字符串;\
                            3.第二个键值对的Key是字符串description,value是该函数的函数的功能说明,也是字符串;\
                            4.第三个键值对的Key是字符串parameters,value是一个JSON Schema对象,用于说明该函数的参数输入规范。\
                            5.输出结果必须是一个JSON格式的字典,只输出这个字典即可,前后不需要任何前后修饰或说明的语句' % chen_ming_function_name
            
            
            assistant_message1 = json.dumps(chen_ming_function)
            
            user_prompt = '现在有另一个函数,函数名为:%s;函数说明为:%s;\
                          请帮我仿造类似的格式为当前函数创建一个function对象。' % (function_name, function_description)

            response = openai.ChatCompletion.create(
                              model="gpt-4-0613",
                              messages=[
                                {
    
    "role": "user", "name":"example_user", "content": user_message1},
                                {
    
    "role": "assistant", "name":"example_assistant", "content": assistant_message1},
                                {
    
    "role": "user", "name":"example_user", "content": user_prompt}]
                            )
            functions.append(json.loads(response.choices[0].message['content']))
        return functions
    
    max_attempts = 3
    attempts = 0

    while attempts < max_attempts:
        try:
            functions = functions_generate(functions_list)
            break  # 如果代码成功执行,跳出循环
        except Exception as e:
            attempts += 1  # 增加尝试次数
            print("发生错误:", e)
            if attempts == max_attempts:
                print("已达到最大尝试次数,程序终止。")
                raise  # 重新引发最后一个异常
            else:
                print("正在重新运行...")
    return functions

実行結果を見てみましょう。

画像-20230727162659259

関数パラメータの記述に問題はありません。次に、チャット モデルが次の形式を満たすパラメータを正常に作成できるかどうかをテストします。

response = openai.ChatCompletion.create(
        model="gpt-4-0613",
        messages=[{
    
    "role": "user", "content": '请帮我查下我Gmail邮箱中最后一封邮件信息'}],
        functions=functions,
        function_call="auto",  
    )

結果を見てください:

画像-20230728083657580

  • ステップ 4: 対話効果を検証する

最後に、対話の効果をテストするために、外部関数呼び出しを自動的に実行する前に作成したチャット対話モデル関数とマルチラウンド対話関数をインポートします。

def run_conversation(messages, functions_list=None, model="gpt-4-0613"):
    """
    能够自动执行外部函数调用的Chat对话模型
    :param messages: 必要参数,字典类型,输入到Chat模型的messages参数对象
    :param functions_list: 可选参数,默认为None,可以设置为包含全部外部函数的列表对象
    :param model: Chat模型,可选参数,默认模型为gpt-4
    :return:Chat模型输出结果
    """
    # 如果没有外部函数库,则执行普通的对话任务
    if functions_list == None:
        response = openai.ChatCompletion.create(
                        model=model,
                        messages=messages,
                        )
        response_message = response["choices"][0]["message"]
        final_response = response_message["content"]
        
    # 若存在外部函数库,则需要灵活选取外部函数并进行回答
    else:
        # 创建functions对象
        functions = auto_functions(functions_list)
        # 创建外部函数库字典
        available_functions = {
    
    func.__name__: func for func in functions_list}

        # first response
        response = openai.ChatCompletion.create(
                        model=model,
                        messages=messages,
                        functions=functions,
                        function_call="auto")
        response_message = response["choices"][0]["message"]

        # 判断返回结果是否存在function_call,即判断是否需要调用外部函数来回答问题
        if response_message.get("function_call"):
            # 需要调用外部函数
            # 获取函数名
            function_name = response_message["function_call"]["name"]
            # 获取函数对象
            fuction_to_call = available_functions[function_name]
            # 获取函数参数
            function_args = json.loads(response_message["function_call"]["arguments"])
            # 将函数参数输入到函数中,获取函数计算结果
            function_response = fuction_to_call(**function_args)

            # messages中拼接first response消息
            messages.append(response_message)  
            # messages中拼接函数输出结果
            messages.append(
                {
    
    
                    "role": "function",
                    "name": function_name,
                    "content": function_response,
                }
            )  
            # 第二次调用模型
            second_response = openai.ChatCompletion.create(
                model=model,
                messages=messages,
            )  
            # 获取最终结果
            final_response = second_response["choices"][0]["message"]["content"]
        else:
            final_response = response_message["content"]
    
    return final_response
def chat_with_model(functions_list=None, 
                    prompt="你好呀", 
                    model="gpt-4-0613", 
                    system_message=[{
    
    "role": "system", "content": "你是以为乐于助人的助手。"}]):
    
    messages = system_message
    messages.append({
    
    "role": "user", "content": prompt})
    
    while True:           
        answer = run_conversation(messages=messages, 
                                    functions_list=functions_list, 
                                    model=model)
        
        
        print(f"模型回答: {
      
      answer}")

        # 询问用户是否还有其他问题
        user_input = input("您还有其他问题吗?(输入退出以结束对话): ")
        if user_input == "退出":
            break

        # 记录用户回答
        messages.append({
    
    "role": "user", "content": user_input})

外部関数 get_latest_email が取り込まれていない場合の最初のテスト:

画像-20230728084323222

外部関数 get_latest_email をテストする場合:

画像-20230728084651589

3.2 チャットモデルにメール送信機能を追加

  • ステップ 1: 認可を再取得する

上記で作成されたトークンには、実際には電子メールを読み取るための API 認可のみが含まれており、電子メールを送信するための API 認可は含まれていませんそのため、まず API 権限の変更、つまり Gmail からメールを送信するための API 認可を再度申請する必要があります。コードの前の認証部分を使用して実行し、送信権限の認証ファイルをローカルの token_send.json ファイルに保存します。コードは次のとおりです。

SCOPES = ['https://www.googleapis.com/auth/gmail.send']

flow = InstalledAppFlow.from_client_secrets_file(
                'credentials-web.json', SCOPES)
creds = flow.run_local_server(port=8000, access_type='offline', prompt='consent')

with open('token_send.json', 'w') as token:
    token.write(creds.to_json())

ここでの認証方法がわからない場合は、Gmail API の OAuth 認証の部分を参照してください。

画像-20230728085549688

**Google Cloud API の(クラス)API は、使用時に異なる API 機能に応じて複数の認可に分割されます。**違いは、SCOPES 変数の設定にあります。SCOPES は複数のアドレスを含むリストであり、各アドレスは異なるリクエストの送信アドレスを表します。つまり、異なる API 権限を表します。アドレスの末尾は gmail です。 send はメール送信用の API を示し、末尾の gmail.readonly はメールの読み取りのみを可能にすることを示します。特定の API 関数については、公式の説明を参照してください: https://developers.google.com/gmail/api/auth/scopes?hl=zh_CN

複数の権限を含む認可ファイルを一度に取得することも可能です。このとき、次のように SCOPES を定義できます。このとき、認可ファイルでは、実行ファイルの読み取りと送信を同時に許可できます。

SCOPES = ['https://www.googleapis.com/auth/gmail.send','https://www.googleapis.com/auth/gmail.readonly']
  • ステップ 2: 送信機能をテストする

認証が完了するとGmailの送信機能が使えるようになりますので、認証ファイルをtoken_send.jsonに変更します 具体的なコード実装手順は以下の通りです。

from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
from email.mime.text import MIMEText
import base64

# 从本地文件中加载凭据
creds = Credentials.from_authorized_user_file('token_send.json')

# 创建 Gmail API 客户端
service = build('gmail', 'v1', credentials=creds)

def create_message(sender, to, subject, message_text):
    """创建一个MIME邮件"""
    message = MIMEText(message_text)
    message['to'] = to
    message['from'] = sender
    message['subject'] = subject
    raw_message = base64.urlsafe_b64encode(message.as_string().encode('utf-8')).decode('utf-8')
    return {
    
    
        'raw': raw_message
    }

def send_message(service, user_id, message):
    """发送邮件"""
    try:
        sent_message = service.users().messages().send(userId=user_id, body=message).execute()
        print(f'Message Id: {
      
      sent_message["id"]}')
        return sent_message
    except Exception as e:
        print(f'An error occurred: {
      
      e}')
        return None

# 创建邮件,发件人、收件邮箱、邮件主题和邮件内容
message = create_message('me', '[email protected]', '测试', '测试Gmail API 的邮件发送功能')

# 发送邮件
send_message(service, 'me', message)

結果を見てください:

画像-20230728090024864

MIME (MultiPurpose Internet Mail Extensions、MultiPurpose Internet Mail Extensions) は、元の電子メール仕様 (ASCII テキストのみを処理できる) を拡張して、電子メールがテキスト (非 ASCII 文字を含む) などの他のタイプのデータをサポートできるようにするインターネット標準です。 、画像、音声、ビデオ、およびアプリケーション データ

Python では、email.mime モジュールは MIME メッセージを作成および変更するためのクラスを提供します。これらのクラスを使用すると、テキスト、HTML、添付ファイル、埋め込み画像など、複数のデータ型を含む複雑なメール メッセージを作成できます。作成されたメッセージ オブジェクトを表示できます。

画像-20230728090314516

send_message = service.users().messages().send(userId=user_id, body=message).execute() によって Gmail API を呼び出すこともできます。

受信メールを確認してください:

画像-20230728090107421

  • ステップ 3: チャット モデルによって呼び出すことができる外部関数をカプセル化する
def send_email(to, subject, message_text):
    """
    借助Gmail API创建并发送邮件函数
    :param to: 必要参数,字符串类型,用于表示邮件发送的目标邮箱地址;
    :param subject: 必要参数,字符串类型,表示邮件主题;
    :param message_text: 必要参数,字符串类型,表示邮件全部正文;
    :return:返回发送结果字典,若成功发送,则返回包含邮件ID和发送状态的字典。
    """
    
    creds_file='token_send.json'
    
    def create_message(to, subject, message_text):
        """创建一个MIME邮件"""
        message = MIMEText(message_text)
        message['to'] = to
        message['from'] = 'me'
        message['subject'] = subject
        raw_message = base64.urlsafe_b64encode(message.as_string().encode('utf-8')).decode('utf-8')
        return {
    
    
            'raw': raw_message
        }

    def send_message(service, user_id, message):
        """发送邮件"""
        try:
            sent_message = service.users().messages().send(userId=user_id, body=message).execute()
            print(f'Message Id: {
      
      sent_message["id"]}')
            return sent_message
        except Exception as e:
            print(f'An error occurred: {
      
      e}')
            return None

    # 从本地文件中加载凭据
    creds = Credentials.from_authorized_user_file(creds_file)

    # 创建 Gmail API 客户端
    service = build('gmail', 'v1', credentials=creds)

    message = create_message(to, subject, message_text)
    res = send_message(service, 'me', message)

    return json.dumps(res)
  • ステップ 3: 関数パラメータをテストする
functions = auto_functions(functions_list)
functions

画像-20230728091143064

関数パラメータの作成に問題はありません。さらに、チャット モデルが関数の必要なパラメータを正常に作成できるかどうかをテストします。

response = openai.ChatCompletion.create(
        model="gpt-4-0613",
        messages=[{
    
    "role": "user", "content": '我想发送一个Gmail邮件,主要内容是:让小陈明天早上9点半来我办公室开会,商量一下我的100亿该怎么花'}],
        functions=functions,
        function_call="auto"
    )

response

返された結果を見てください。

画像-20230728095249243

モデルは電子メールの件名と内容を意味的に作成します。

  • ステップ 4: 複数ラウンドの対話の検証

外部関数 send_email を呼び出さない場合と、send_email を呼び出した場合のモデルの処理を見てみましょう。

画像-20230728101052498

次に、メールボックス内の受信メールを確認します。

画像-20230728101206448

これまでのところ、電子メール送信機能はチャット モデルに完全に統合されています。

3.3 チャットモデルでメール送信機能とクエリ機能を同時に呼び出す

メール送信関数を個別に実行した後、ダイアログでこれら 2 つの関数を同時に呼び出すことができます結局のところ、ほとんどの AI アプリケーションは、アプリケーションの特定の側面を中心とした多機能の集合体であり、たとえば、スマート メール アシスタントの AI アプリケーションの場合、メールの送受信が最も基本的な機能要件でなければなりません。

Chat モデルに受信機能と送信機能の両方を統合するには、get_latest_email と send_email を同時に function_list に追加するだけです。コードは次のとおりです。

functions_list = [get_latest_email, send_email]
chat_with_model(functions_list=functions_list,
                system_message=[{
    
    "role": "system", "content": "小陈的邮箱地址是:[email protected]"}]

結果を見てください:

画像-20230729094357803

画像-20230729094428977

これまで、チャットの会話では送信と受信の機能が同時に呼び出されてきました。

4. まとめ

上記の実践を通じて、チャット モデルに統合される外部ツール API が増えるにつれて、アプリケーション全体がよりインテリジェントになることがわかります。関数呼び出しを利用した AI アプリケーション開発のプロセスでは、外部関数ライブラリの機能範囲が、現在の AI アプリケーションが「賢い」かどうかを判断する鍵となります

しかし実際には、外部ツール API を含む外部関数を作成するプロセスは単純ではなく、対応する API 権限を取得するために何度も試行する必要があるという問題について考える必要があります。外部関数コードをスムーズに作成するには、API 呼び出しに関する一定の知識も必要です。スマートメールを送受信するアプリケーションを作成するだけでも、APIの取得方法や関数の記述方法に多くの時間がかかります。

しかし実際には、この開発プロセスはある程度簡素化される可能性があり、簡素化の中心となるアイデアは、AI (チャット モデル) を使用して AI アプリケーションの開発を支援することです。

最後に、この記事を読んでいただきありがとうございます! 何か得をしたと感じたら、ぜひ「いいね!」「ブックマーク」「フォロー」をしてください。これが私が創作を続けるモチベーションです。ご質問やご提案がございましたら、コメント欄にメッセージを残してください。できる限りお答えし、フィードバックを受け付けます。知りたい特定のトピックがございましたら、お知らせください。喜んでそれに関する記事を書きます。ご支援に感謝し、あなたと一緒に成長することを楽しみにしています!

おすすめ

転載: blog.csdn.net/Lvbaby_/article/details/131991684