תחילת העבודה עם אחזור סמנטי

להצגה ב-ai.google.dev רוצים לנסות notebook של Colab? הצגת ה-notebook ב-GitHub

סקירה כללית

מודלים גדולים של שפה (LLMs) יכולים ללמוד יכולות חדשות בלי לאמן אותם ישירות. עם זאת, מודלים מסוג LLM ידועים כ"הזות" (hallucination) כשהמטרה שלהם היא לתת תשובות לשאלות שהם לא אומנו. הסיבה לכך היא, בין היתר, שמודלים מסוג LLM לא מודעים לאירועים אחרי האימון. בנוסף, קשה מאוד לעקוב אחרי המקורות שמהם מודלים של LLM שואבים את התשובות שלהם. כדי ליצור אפליקציות מהימנות שניתן להתאים לעומס, חשוב שמודל שפה גדול (LLM) יספק תשובות שמבוססות על עובדות ויכול לצטט את מקורות המידע שלו.

גישה נפוצה שמשמשת כדי להתגבר על המגבלות האלה נקראת Retrieval Augmented Generation (RAG), שמרחיבה את ההנחיה שנשלחת ל-LLM בנתונים רלוונטיים שאוחזרו ממאגר ידע חיצוני באמצעות מנגנון לאחזור מידע (IR). מאגר הידע יכול להיות קורפוסים משלכם של מסמכים, מסדי נתונים או ממשקי API.

במסמך ה-notebook הזה מוסבר תהליך עבודה לשיפור התשובה של LLM על ידי הרחבת הידע שלו באמצעות מאגרי טקסט חיצוניים וביצוע אחזור מידע סמנטי כדי לענות על שאלות באמצעות ה-Semantic Retriever ו-API של שאלות ותשובות (AQA) עם מאפיינים של Generative Language API.

הגדרה

ייבוא של Generative Language API

# Install the Client library (Semantic Retriever is only supported for versions >0.4.0)
pip install -U google.ai.generativelanguage

אמת

באמצעות Semantic Retriever API אפשר לבצע חיפוש סמנטי בנתונים שלכם. מכיוון שמדובר בנתונים שלכם, צריך להשתמש באמצעי בקרת גישה מחמירים יותר מאשר במפתחות API. כדאי לבצע אימות באמצעות OAuth באמצעות חשבונות שירות או באמצעות פרטי הכניסה של המשתמש.

במדריך למתחילים הזה נעשה שימוש בגישה פשוטה לאימות שמיועדת לסביבת בדיקה, ובדרך כלל קל יותר להתחיל בהגדרות של חשבונות שירות. בסביבת ייצור, כדאי לקרוא על אימות והרשאה לפני שבוחרים את פרטי הכניסה המתאימים לאפליקציה.

הגדרת OAuth באמצעות חשבונות שירות

כדי להגדיר את OAuth באמצעות חשבונות שירות:

  1. מפעילים את Generative Language API.

  1. יוצרים את חשבון השירות לפי המסמכים.

    • אחרי שיוצרים את חשבון השירות, יוצרים מפתח לחשבון השירות.

  1. מעלים את הקובץ של חשבון השירות באמצעות סמל הקובץ בסרגל הצד ימין, ואז סמל ההעלאה, כפי שמוצג בצילום המסך שבהמשך.

    • משנים את שם הקובץ שהועלו ל-service_account_key.json או משנים את המשתנה service_account_file_name בקוד שבהמשך.

pip install -U google-auth-oauthlib
service_account_file_name = 'service_account_key.json'

from google.oauth2 import service_account

credentials = service_account.Credentials.from_service_account_file(service_account_file_name)

scoped_credentials = credentials.with_scopes(
    ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/generative-language.retriever'])

מפעילים את ספריית הלקוח באמצעות פרטי הכניסה של חשבון השירות.

import google.ai.generativelanguage as glm
generative_service_client = glm.GenerativeServiceClient(credentials=scoped_credentials)
retriever_service_client = glm.RetrieverServiceClient(credentials=scoped_credentials)
permission_service_client = glm.PermissionServiceClient(credentials=scoped_credentials)

יצירת מאגר טקסטים

באמצעות Semantic Retriever API אפשר להגדיר עד 5 מאגרי טקסט מותאמים אישית לכל פרויקט. אפשר לציין את אחד מהשדות הבאים כשמגדירים את הקורפוסים:

  • name: שם המשאב (המזהה) של Corpus. צריך להכיל רק 40 תווים אלפאנומריים לכל היותר. אם השדה name ריק בזמן היצירה, ייווצר שם ייחודי באורך מקסימלי של 40 תווים, עם קידומת מהשדה display_name וסיומת אקראית בת 12 תווים.
  • display_name: השם המוצג של Corpus שקריא לאנשים. יכול להכיל עד 512 תווים, כולל תווים אלפאנומריים, רווחים ומקפים.
example_corpus = glm.Corpus(display_name="Google for Developers Blog")
create_corpus_request = glm.CreateCorpusRequest(corpus=example_corpus)

# Make the request
create_corpus_response = retriever_service_client.create_corpus(create_corpus_request)

# Set the `corpus_resource_name` for subsequent sections.
corpus_resource_name = create_corpus_response.name
print(create_corpus_response)
name: "corpora/google-for-developers-blog-dqrtz8rs0jg"
display_name: "Google for Developers Blog"
create_time {
  seconds: 1713497533
  nanos: 587977000
}
update_time {
  seconds: 1713497533
  nanos: 587977000
}

אחזור של מאגר הנתונים שנוצר

משתמשים ב-method‏ GetCorpusRequest כדי לגשת באופן פרוגרמטי ל-Corpus שיצרתם למעלה. הערך של הפרמטר name מתייחס לשם המלא של המשאב של Corpus, והוא מוגדר בתא שלמעלה כ-corpus_resource_name. הפורמט הנדרש הוא corpora/corpus-123.

get_corpus_request = glm.GetCorpusRequest(name=corpus_resource_name)

# Make the request
get_corpus_response = retriever_service_client.get_corpus(get_corpus_request)

# Print the response
print(get_corpus_response)

יצירת מסמך

Corpus יכול להכיל עד 10,000 Document. במהלך הגדרת המסמכים, ניתן לציין כל אחד מהשדות הבאים:

  • name: שם המשאב (המזהה) של Document. חייב להכיל עד 40 תווים (אלפאנומריים או מקפים בלבד). המזהה לא יכול להתחיל או להסתיים במקף. אם השם יהיה ריק בזמן היצירה, המערכת תיצור שם ייחודי מ-display_name יחד עם סיומת אקראית בת 12 תווים.
  • display_name: השם המוצג בפורמט קריא לאנשים. חייב להכיל עד 512 תווים, כולל תווים אלפאנומריים, רווחים ומקפים.

Documents תומכים גם בעד 20 שדות custom_metadata שצוינו על ידי המשתמש, שצוינו כצמדי מפתח-ערך. מטא-נתונים בהתאמה אישית יכולים להיות מחרוזות, רשימות של מחרוזות או מספרים. שימו לב שרשימות של מחרוזות יכולות לתמוך ב-10 ערכים לכל היותר, וערכים מספריים מיוצגים כמספרים בנקודה צפה ב-API.

# Create a document with a custom display name.
example_document = glm.Document(display_name="Introducing Project IDX, An Experiment to Improve Full-stack, Multiplatform App Development")

# Add metadata.
# Metadata also supports numeric values not specified here
document_metadata = [
    glm.CustomMetadata(key="url", string_value="https://developers.googleblog.com/2023/08/introducing-project-idx-experiment-to-improve-full-stack-multiplatform-app-development.html")]
example_document.custom_metadata.extend(document_metadata)

# Make the request
# corpus_resource_name is a variable set in the "Create a corpus" section.
create_document_request = glm.CreateDocumentRequest(parent=corpus_resource_name, document=example_document)
create_document_response = retriever_service_client.create_document(create_document_request)

# Set the `document_resource_name` for subsequent sections.
document_resource_name = create_document_response.name
print(create_document_response)

אחזור המסמך שנוצר

משתמשים ב-method‏ GetDocumentRequest כדי לגשת באופן פרוגרמטי למסמך שיצרתם למעלה. הערך של הפרמטר name מתייחס לשם המשאב המלא של המסמך, והוא מוגדר בתא שלמעלה כ-document_resource_name. הפורמט הנדרש הוא corpora/corpus-123/documents/document-123.

get_document_request = glm.GetDocumentRequest(name=document_resource_name)

# Make the request
# document_resource_name is a variable set in the "Create a document" section.
get_document_response = retriever_service_client.get_document(get_document_request)

# Print the response
print(get_document_response)

הטמעה וקיצוץ של מסמך

כדי לשפר את הרלוונטיות של תוכן שמוחזר על ידי מסד נתונים וקטורי במהלך אחזור סמנטי, מומלץ לחלק מסמכים גדולים לחלקים או למקטעים קטנים יותר בזמן הטמעת המסמך.

Chunk הוא רכיב משנה של Document, שמטופל כיחידה עצמאית למטרות של ייצוג ושל אחסון של וקטורים. אפשר להוסיף ל-Chunk עד 2,043 אסימונים. Corpus יכול להכיל עד מיליון Chunk.

בדומה ל-Document, גם Chunks תומך בעד 20 שדות custom_metadata שמוגדרים על ידי המשתמש, שצוינו כצמדי מפתח-ערך. מטא-נתונים מותאמים אישית יכולים להיות מחרוזות, רשימות של מחרוזות או מספרים. חשוב לזכור שאפשר להוסיף לרשימות של מחרוזות עד 10 ערכים, וערכים מספריים מיוצגים כמספרים בספרות עשרוניות ב-API.

במדריך הזה נעשה שימוש ב-HtmlChunker בקוד פתוח של Google.

מקטעי נתונים אחרים שבהם אפשר להשתמש: LangChain או LlamaIndex.

הטמעת נתוני HTML ומקטע נתונים באמצעות HtmlChunker

!pip install google-labs-html-chunker

from google_labs_html_chunker.html_chunker import HtmlChunker

from urllib.request import urlopen

אחזור ה-DOM של HTML לאתר. כאן ה-HTML נקרא ישירות, אבל עדיף לקבל HTML אחרי רינדור שכולל HTML עם הזרקת JavaScript, כמו document.documentElement.innerHTML.

with(urlopen("https://developers.googleblog.com/2023/08/introducing-project-idx-experiment-to-improve-full-stack-multiplatform-app-development.html")) as f:
  html = f.read().decode("utf-8")

מחלקים את מסמך הטקסט לקטעים ויוצרים Chunk מהקטעים האלה. בשלב הזה נוצרים אובייקטי Chunk עצמם, ובקטע הבא הם מועלים ל-Semantic Retriever API.

# Chunk the file using HtmlChunker
chunker = HtmlChunker(
    max_words_per_aggregate_passage=200,
    greedily_aggregate_sibling_nodes=True,
    html_tags_to_exclude={"noscript", "script", "style"},
)
passages = chunker.chunk(html)
print(passages)


# Create `Chunk` entities.
chunks = []
for passage in passages:
    chunk = glm.Chunk(data={'string_value': passage})
    # Optionally, you can add metadata to a chunk
    chunk.custom_metadata.append(glm.CustomMetadata(key="tags",
                                                    string_list_value=glm.StringList(
                                                        values=["Google For Developers", "Project IDX", "Blog", "Announcement"])))
    chunk.custom_metadata.append(glm.CustomMetadata(key="chunking_strategy",
                                                    string_value="greedily_aggregate_sibling_nodes"))
    chunk.custom_metadata.append(glm.CustomMetadata(key = "publish_date",
                                                    numeric_value = 20230808))
    chunks.append(chunk)
print(chunks)

יצירת קטעי קוד בכמות גדולה

יצירת מקטעים בכמות גדולה. אפשר לציין עד 100 קטעים לכל בקשת אצווה.

משתמשים ב-CreateChunk() כדי ליצור מקטע יחיד.

# Option 1: Use HtmlChunker in the section above.
# `chunks` is the variable set from the section above.
create_chunk_requests = []
for chunk in chunks:
  create_chunk_requests.append(glm.CreateChunkRequest(parent=document_resource_name, chunk=chunk))

# Make the request
request = glm.BatchCreateChunksRequest(parent=document_resource_name, requests=create_chunk_requests)
response = retriever_service_client.batch_create_chunks(request)
print(response)

לחלופין, אפשר ליצור קטעים בלי להשתמש ב-HtmlChunker.

# Add up to 100 CreateChunk requests per batch request.
# document_resource_name is a variable set in the "Create a document" section.
chunks = []
chunk_1 = glm.Chunk(data={'string_value': "Chunks support user specified metadata."})
chunk_1.custom_metadata.append(glm.CustomMetadata(key="section",
                                                  string_value="Custom metadata filters"))
chunk_2 = glm.Chunk(data={'string_value': "The maximum number of metadata supported is 20"})
chunk_2.custom_metadata.append(glm.CustomMetadata(key = "num_keys",
                                                  numeric_value = 20))
chunks = [chunk_1, chunk_2]
create_chunk_requests = []
for chunk in chunks:
  create_chunk_requests.append(glm.CreateChunkRequest(parent=document_resource_name, chunk=chunk))

# Make the request
request = glm.BatchCreateChunksRequest(parent=document_resource_name, requests=create_chunk_requests)
response = retriever_service_client.batch_create_chunks(request)
print(response)

הצגת רשימה של Chunk וקבלת המצב

אפשר להשתמש בשיטה ListChunksRequest כדי לקבל את כל הפריטים מסוג 'Chunk' כרשימה מחלקת לדפים, עם מגבלת גודל של 100 שניות Chunk בכל דף, שממוינים בסדר עולה של Chunk.create_time. אם לא מציינים מגבלה, יוחזרו עד 10 נקודות Chunk.

מציינים את הערך של next_page_token שהוחזר בתגובה ListChunksRequest כארגומנטים לבקשה הבאה כדי לאחזר את הדף הבא. הערה: כשמחלקים את הדפים, כל הפרמטרים האחרים שסופקו ל-ListChunks חייבים להתאים לקריאה שסיפקה את אסימון הדף.

כל הערכים של 'Chunk' מחזירים state. אפשר להשתמש במדד הזה כדי לבדוק את המצב של Chunks לפני שליחת שאילתה על Corpus. המצבים של Chunk כוללים את UNSPECIFIED, PENDING_PROCESSING, ACTIVE ו-FAILED. אפשר לשלוח שאילתות רק על ACTIVE Chunk.

# Make the request
request = glm.ListChunksRequest(parent=document_resource_name)
list_chunks_response = retriever_service_client.list_chunks(request)
for index, chunks in enumerate(list_chunks_response.chunks):
  print(f'\nChunk # {index + 1}')
  print(f'Resource Name: {chunks.name}')
  # Only ACTIVE chunks can be queried.
  print(f'State: {glm.Chunk.State(chunks.state).name}')

הטמעת מסמך נוסף

צריך להוסיף עוד Document דרך HtmlChunker ולהוסיף פילטרים.

# Create a document with a custom display name.
example_document = glm.Document(display_name="How it’s Made: Interacting with Gemini through multimodal prompting")

# Add document metadata.
# Metadata also supports numeric values not specified here
document_metadata = [
    glm.CustomMetadata(key="url", string_value="https://developers.googleblog.com/2023/12/how-its-made-gemini-multimodal-prompting.html")]
example_document.custom_metadata.extend(document_metadata)

# Make the CreateDocument request
# corpus_resource_name is a variable set in the "Create a corpus" section.
create_document_request = glm.CreateDocumentRequest(parent=corpus_resource_name, document=example_document)
create_document_response = retriever_service_client.create_document(create_document_request)

# Set the `document_resource_name` for subsequent sections.
document_resource_name = create_document_response.name
print(create_document_response)

# Chunks - add another webpage from Google for Developers
with(urlopen("https://developers.googleblog.com/2023/12/how-its-made-gemini-multimodal-prompting.html")) as f:
  html = f.read().decode("utf-8")

# Chunk the file using HtmlChunker
chunker = HtmlChunker(
    max_words_per_aggregate_passage=100,
    greedily_aggregate_sibling_nodes=False,
)
passages = chunker.chunk(html)

# Create `Chunk` entities.
chunks = []
for passage in passages:
    chunk = glm.Chunk(data={'string_value': passage})
    chunk.custom_metadata.append(glm.CustomMetadata(key="tags",
                                                    string_list_value=glm.StringList(
                                                        values=["Google For Developers", "Gemini API", "Blog", "Announcement"])))
    chunk.custom_metadata.append(glm.CustomMetadata(key="chunking_strategy",
                                                    string_value="no_aggregate_sibling_nodes"))
    chunk.custom_metadata.append(glm.CustomMetadata(key = "publish_date",
                                                    numeric_value = 20231206))
    chunks.append(chunk)

# Make the request
create_chunk_requests = []
for chunk in chunks:
  create_chunk_requests.append(glm.CreateChunkRequest(parent=document_resource_name, chunk=chunk))
request = glm.BatchCreateChunksRequest(parent=document_resource_name, requests=create_chunk_requests)
response = retriever_service_client.batch_create_chunks(request)
print(response)

שליחת שאילתה לגוף הטקסט

אפשר להשתמש בשיטה QueryCorpusRequest כדי לבצע חיפוש סמנטי כדי לקבל פסקאות רלוונטיות.

  • results_count: מציינים את מספר קטעי הטקסט שרוצים להחזיר. המספר המקסימלי הוא 100. אם לא צוין אחרת, ה-API יחזיר עד 10 שניות Chunk.
  • metadata_filters: סינון לפי chunk_metadata או document_metadata. כל MetadataFilter צריך להתאים למפתח ייחודי. אובייקטים מרובים מסוג MetadataFilter מחוברים באמצעות AND לוגיים. תנאים דומים של מסנני מטא-נתונים מצורפים באמצעות OR לוגיים. מספר דוגמאות:
(year >= 2020 OR year < 2010) AND (genre = drama OR genre = action)

metadata_filter = [
  {
    key = "document.custom_metadata.year"
    conditions = [
      {int_value = 2020, operation = GREATER_EQUAL},
      {int_value = 2010, operation = LESS}]
  },
  {
    key = "document.custom_metadata.genre"
    conditions = [
      {string_value = "drama", operation = EQUAL},
      {string_value = "action", operation = EQUAL} }]
  }]

שימו לב שרק ערכים מספריים תומכים ב-AND לאותו מפתח. בערכי מחרוזת אפשר להשתמש רק ב-OR עבור אותו מפתח.

("Google for Developers" in tags) and (20230314 > publish_date)

metadata_filter = [
 {
    key = "chunk.custom_metadata.tags"
    conditions = [
    {string_value = 'Google for Developers', operation = INCLUDES},
  },
  {
    key = "chunk.custom_metadata.publish_date"
    conditions = [
    {numeric_value = 20230314, operation = GREATER_EQUAL}]
  }]
user_query = "What is the purpose of Project IDX?"
results_count = 5

# Add metadata filters for both chunk and document.
chunk_metadata_filter = glm.MetadataFilter(key='chunk.custom_metadata.tags',
                                           conditions=[glm.Condition(
                                              string_value='Google For Developers',
                                              operation=glm.Condition.Operator.INCLUDES)])

# Make the request
# corpus_resource_name is a variable set in the "Create a corpus" section.
request = glm.QueryCorpusRequest(name=corpus_resource_name,
                                 query=user_query,
                                 results_count=results_count,
                                 metadata_filters=[chunk_metadata_filter])
query_corpus_response = retriever_service_client.query_corpus(request)
print(query_corpus_response)

מענה לשאלות עם שיוך

משתמשים בשיטה GenerateAnswer כדי לבצע מענה לשאלות עם שיוך למאפיינים במסמך, במאגר נתונים או בקבוצה של קטעים.

'מענה לשאלות עם שיוך (AQA)' מתייחס למתן תשובות לשאלות שמבוססות על הקשר נתון, תוך מתן שיוך(attribution) תוך צמצום הזיה.

ל-GenerateAnswer יש כמה יתרונות על פני שימוש ב-LLM לא מכוונן, במקרים שבהם רוצים לבצע בקרת איכות:

  • המודל הבסיסי אומן להחזיר רק תשובות שמבוססות על ההקשר שסופק.
  • הוא מזהה שיוך (קטעים מההקשר שסופק ועזרו לתת את התשובה). שיוך מאפשר למשתמש לאמת את התשובה.
  • הוא מעריך את הערך של answerable_probability עבור צמד נתון (שאלה, הקשר), וכך מאפשר לכם לשנות את התנהגות המוצר בהתאם לסבירות שהתשובה שתתקבל תהיה מבוססת ונכונה.

answerable_probability והבעיה 'אין לי מושג'

במקרים מסוימים, התשובה הטובה ביותר לשאלה היא למעשה 'אין לי תשובה'. לדוגמה, אם ההקשר שסופק לא מכיל את התשובה לשאלה, השאלה נחשבת ל'לא ניתנת להשבה'.

המודל של AQA מזהה בקלות מקרים כאלה. הוא יכול גם להבחין בין רמות של יכולת מענה לבין רמות של חוסר יכולת מענה.

עם זאת, באמצעות ה-API של GenerateAnswer, ההחלטה הסופית היא בידיכם:

  • תמיד ניסיון להחזיר תשובה בסיסית – גם אם סביר יחסית שהתשובה הזו מבוססת על תשובה נכונה ונכונה.
  • החזרת הערך answerable_probability – אומדן המודל של הסבירות שהתשובה מבוססת ונכונה.

הערך של answerable_probability נמוך יכול לנבוע מאחד או יותר מהגורמים הבאים:

  • המודל לא בטוח שהתשובה שלו נכונה.
  • המודל לא בטוח שהתשובה שלו מבוססת על קטעי הטקסט שצוינו. יכול להיות שהתשובה נובעת במקום זאת מידע כללי. לדוגמה: question="1+1=?", passages=["2+2=4”]answer=2, answerable_probability=0.02
  • המודל סיפק מידע רלוונטי שלא ענה באופן מלא על השאלה. דוגמה: question="Is it available in my size?, passages=["Available in sizes 5-11"]answer="Yes it is available in sizes 5-11", answerable_probability=0.03"
  • לא נשלחה שאלה תקינה ב-GenerateAnswerRequest.

מכיוון שערך נמוך של answerable_probability מציין שהתשובה של GenerateAnswerResponse.answer כנראה שגויה או לא מבוססת, מומלץ מאוד לעבד את התשובה בהמשך על ידי בדיקת answerable_probability.

כשהערך של answerable_probability נמוך, לקוחות מסוימים עשויים לרצות:

  • להציג למשתמש הקצה הודעה כמו "לא הצלחנו לענות על השאלה הזו".
  • נחזור ל-LLM לשימוש כללי שיענה על השאלה מתוך הידע העולמי. הסף והאופי של חלופות כאלה תלויים בתרחישים שונים לדוגמה. ערך של answerable_probability <= 0.5 הוא סף התחלה טוב.

טיפים מועילים של AQA

למפרטי ה-API המלאים, אפשר לעיין בחומר העזר בנושא GenerateAnswerRequest API.

  • אורך הקטע: מומלץ להשתמש בעד 300 אסימונים לכל קטע.
  • מיון קטעים:
    • אם תספקו את הפרמטר GenerateAnswerRequest.inline_passages, קטעי הטקסט ימוינו לפי רמת הרלוונטיות ההולכת ופוחתת לשאילתה. אם חורגים ממגבלת אורך ההקשר של המודל, הקטעים האחרונים (הכי פחות רלוונטיים) יושמטו.
    • אם תוסיפו GenerateAnswerRequest.semantic_retriever, המיון של הרלוונטיות יתבצע עבורכם באופן אוטומטי.
  • מגבלות: מודל ה-AQA מיועד במיוחד למתן תשובות לשאלות. במקרים אחרים, כמו כתיבה יצירתית, סיכום וכו', צריך להפעיל מודל למטרות כלליות באמצעות GenerateContent.
    • צ'אט: אם הקלט של המשתמש הוא שאלה שאפשר לענות עליה בהקשר מסוים, AQA יכול לענות על שאילתות בצ'אט. אבל אם קלט המשתמש יכול להיות כל סוג של רשומה, מודל למטרות כלליות עשוי להיות בחירה טובה יותר.
  • טמפרטורה:
    • באופן כללי, מומלץ להשתמש בטמפרטורה נמוכה יחסית (כ-0.2) כדי לבצע בדיקת AQA מדויקת.
    • אם תרחיש לדוגמה מסתמך על תוצאות ודאיות, צריך להגדיר את temperature=0.
user_query = "What is the purpose of Project IDX?"
answer_style = "ABSTRACTIVE" # Or VERBOSE, EXTRACTIVE
MODEL_NAME = "models/aqa"

# Make the request
# corpus_resource_name is a variable set in the "Create a corpus" section.
content = glm.Content(parts=[glm.Part(text=user_query)])
retriever_config = glm.SemanticRetrieverConfig(source=corpus_resource_name, query=content)
req = glm.GenerateAnswerRequest(model=MODEL_NAME,
                                contents=[content],
                                semantic_retriever=retriever_config,
                                answer_style=answer_style)
aqa_response = generative_service_client.generate_answer(req)
print(aqa_response)
# Get the metadata from the first attributed passages for the source
chunk_resource_name = aqa_response.answer.grounding_attributions[0].source_id.semantic_retriever_chunk.chunk
get_chunk_response = retriever_service_client.get_chunk(name=chunk_resource_name)
print(get_chunk_response)

אפשרויות נוספות: AQA באמצעות קטעי טקסט בתוך הטקסט

לחלופין, אפשר להעביר את הערך inline_passages כדי להשתמש ישירות בנקודת הקצה של AQA, בלי להשתמש ב-Semantic Retriever API.

user_query = "What is AQA from Google?"
user_query_content = glm.Content(parts=[glm.Part(text=user_query)])
answer_style = "VERBOSE" # or ABSTRACTIVE, EXTRACTIVE
MODEL_NAME = "models/aqa"

# Create the grounding inline passages
grounding_passages = glm.GroundingPassages()
passage_a = glm.Content(parts=[glm.Part(text="Attributed Question and Answering (AQA) refers to answering questions grounded to a given corpus and providing citation")])
grounding_passages.passages.append(glm.GroundingPassage(content=passage_a, id="001"))
passage_b = glm.Content(parts=[glm.Part(text="An LLM is not designed to generate content grounded in a set of passages. Although instructing an LLM to answer questions only based on a set of passages reduces hallucination, hallucination still often occurs when LLMs generate responses unsupported by facts provided by passages")])
grounding_passages.passages.append(glm.GroundingPassage(content=passage_b, id="002"))
passage_c = glm.Content(parts=[glm.Part(text="Hallucination is one of the biggest problems in Large Language Models (LLM) development. Large Language Models (LLMs) could produce responses that are fictitious and incorrect, which significantly impacts the usefulness and trustworthiness of applications built with language models.")])
grounding_passages.passages.append(glm.GroundingPassage(content=passage_c, id="003"))

# Create the request
req = glm.GenerateAnswerRequest(model=MODEL_NAME,
                                contents=[user_query_content],
                                inline_passages=grounding_passages,
                                answer_style=answer_style)
aqa_response = generative_service_client.generate_answer(req)
print(aqa_response)

שיתוף הקורפוס

אתם יכולים לשתף את הקורפוס עם אחרים באמצעות ה-API של CreatePermissionRequest.

מגבלות:

  • יש 2 תפקידים לשיתוף: READER ו-EDITOR.
    • READER יכול לשלוח שאילתות לגוף הטקסט.
    • ל-WRITER יש הרשאות קריאה, וגם אפשרות לערוך ולשתף את הקורפוס.
  • כדי שהקורפוס יהיה ציבורי, צריך להעניק ל-EVERYONE הרשאת קריאה כ-user_type.
# Replace your-email@gmail.com with the email added as a test user in the OAuth Quickstart
shared_user_email = "TODO-your-email@gmail.com" #  @param {type:"string"}
user_type = "USER"
role = "READER"

# Make the request
# corpus_resource_name is a variable set in the "Create a corpus" section.
request = glm.CreatePermissionRequest(
    parent=corpus_resource_name,
    permission=glm.Permission(grantee_type=user_type,
                              email_address=shared_user_email,
                              role=role))
create_permission_response = permission_service_client.create_permission(request)
print(create_permission_response)

מחיקת הקורפוס

משתמשים ב-DeleteCorpusRequest כדי למחוק מאגר של משתמש ואת כל ה-Document וה-Chunk שמשויכים אליו.

חשוב לשים לב שאוסף שאינו ריק יקפיץ שגיאה בלי לציין דגל force=True. אם תגדירו את force=True, כל ה-Chunk והאובייקטים שקשורים ל-Document הזה יימחקו גם הם.

אם force=False (ברירת המחדל) ו-Document מכילים ערכים מסוג Chunk, תוחזר שגיאה מסוג FAILED_PRECONDITION.

# Set force to False if you don't want to delete non-empty corpora.
req = glm.DeleteCorpusRequest(name=corpus_resource_name, force=True)
delete_corpus_response = retriever_service_client.delete_corpus(req)
print("Successfully deleted corpus: " + corpus_resource_name)

סיכום וקריאה נוספת

במדריך הזה הצגנו את ממשקי ה-API של שליפה סמנטית ו-Attributed Question &Answering (AQA) של Generative Language API, והראו איך אפשר להשתמש בהם כדי לאחזר מידע סמנטי של נתוני הטקסט בהתאמה אישית. חשוב לזכור ש-API הזה פועל גם עם מסגרת הנתונים של LlamaIndex. מידע נוסף זמין במדריך.

מומלץ גם לעיין במסמכי ה-API כדי לקבל מידע נוסף על הפונקציות הזמינות האחרות.

נספח: הגדרת OAuth באמצעות פרטי כניסה של משתמשים

כדי להגדיר אימות OAuth, יש לבצע את השלבים הבאים מהמדריך למתחילים של OAuth.

  1. מגדירים את מסך ההסכמה של OAuth.

  2. אישור פרטי כניסה לאפליקציה למחשב. כדי להריץ את ה-notebook הזה ב-Colab, קודם צריך לשנות את שם קובץ פרטי הכניסה (בדרך כלל client_secret_*.json) ל-client_secret.json בלבד. לאחר מכן מעלים את הקובץ באמצעות סמל הקובץ בסרגל הצד שמימין, ואז על סמל ההעלאה, כפי שמוצג בצילום המסך למטה.

# Replace TODO-your-project-name with the project used in the OAuth Quickstart
project_name = "TODO-your-project-name" #  @param {type:"string"}
# Replace TODO-your-email@gmail.com with the email added as a test user in the OAuth Quickstart
email = "TODO-your-email@gmail.com" #  @param {type:"string"}
# Rename the uploaded file to `client_secret.json` OR
# Change the variable `client_file_name` in the code below.
client_file_name = "client_secret.json"

# IMPORTANT: Follow the instructions from the output - you must copy the command
# to your terminal and copy the output after authentication back here.
!gcloud config set project $project_name
!gcloud config set account $email

# NOTE: The simplified project setup in this tutorial triggers a "Google hasn't verified this app." dialog.
# This is normal, click "Advanced" -> "Go to [app name] (unsafe)"
!gcloud auth application-default login --no-browser --client-id-file=$client_file_name --scopes="https://www.googleapis.com/auth/generative-language.retriever,https://www.googleapis.com/auth/cloud-platform"

לאתחל את ספריית הלקוח ולהריץ מחדש את ה-notebook החל מיצירת קורפוס.

import google.ai.generativelanguage as glm

generative_service_client = glm.GenerativeServiceClient()
retriever_service_client = glm.RetrieverServiceClient()
permission_service_client = glm.PermissionServiceClient()