Back to the Couchbase homepageCouchbase logo
Couchbase Developer

  • Docs

    • Integrations
    • SDKs
    • Mobile SDKs

    • AI Developer
    • Backend
    • Full-stack
    • Mobile
    • Ops / DBA

    • Data Modeling
    • Scalability

  • Tutorials

    • Developer Community
    • Ambassador Program
  • Sign In
  • Try Free

PydanticAI RAG with Couchbase Hyperscale and Composite Vector Index

  • Learn how to build a semantic search engine using Couchbase and PydanticAI using Hyperscale and Composite Vector Index.
  • This tutorial demonstrates how to integrate Couchbase's vector search capabilities with PydanticAI using Hyperscale and Composite Vector Index.
  • You'll understand how to perform Retrieval-Augmented Generation (RAG) using PydanticAI and Couchbase with Hyperscale and Composite Vector Index optimization.

View Source

Introduction

In this guide, we will walk you through building a powerful semantic search engine using Couchbase as the backend database, OpenAI as the embedding and LLM provider, and PydanticAI as an agent orchestrator. Semantic search goes beyond simple keyword matching by understanding the context and meaning behind the words in a query, making it an essential tool for applications that require intelligent information retrieval. This tutorial is designed to be beginner-friendly, with clear, step-by-step instructions that will equip you with the knowledge to create a fully functional semantic search system using Couchbase Hyperscale and Composite Vector Index from scratch. Alternatively if you want to perform semantic search using the Search Vector Index, please take a look at this.

How to run this tutorial

This tutorial is available as a Jupyter Notebook (.ipynb file) that you can run interactively. You can access the original notebook here.

You can either download the notebook file and run it on Google Colab or run it on your system by setting up the Python environment.

Before you start

Get Credentials for OpenAI

  • Please follow the instructions to generate the OpenAI credentials.

Create and Deploy Your Free Tier Operational cluster on Capella

To get started with Couchbase Capella, create an account and use it to deploy a forever free tier operational cluster. This account provides you with an environment where you can explore and learn about Capella with no time constraint.

To learn more, please follow the instructions.

Note: To run this tutorial, you will need Capella with Couchbase Server version 8.0 or above as Hyperscale and Composite Vector Index search is supported only from version 8.0

Couchbase Capella Configuration

When running Couchbase using Capella, the following prerequisites need to be met.

  • Create the database credentials to access the required bucket (Read and Write) used in the application.

  • Allow access to the Cluster from the IP on which the application is running.

Setting the Stage: Installing Necessary Libraries

To build our semantic search engine, we need a robust set of tools. The libraries we install handle everything from connecting to databases to performing complex machine learning tasks. Each library has a specific role: Couchbase libraries manage database operations, LangChain handles AI model integrations, and OpenAI provides advanced AI models for generating embeddings and understanding natural language. By setting up these libraries, we ensure our environment is equipped to handle the data-intensive and computationally complex tasks required for semantic search.

%pip install --quiet datasets==5.0.0 langchain-couchbase==1.1.0 langchain-openai==1.4.1 python-dotenv==1.2.2 pydantic-ai==2.17.0

Importing Necessary Libraries

The script starts by importing a series of libraries required for various tasks, including handling JSON, logging, time tracking, Couchbase connections, embedding generation, and dataset loading. These libraries provide essential functions for working with data, managing database connections, and processing machine learning models.

import getpass
import json
import logging
import os
import time
from datetime import timedelta

from couchbase.auth import PasswordAuthenticator
from couchbase.cluster import Cluster
from couchbase.exceptions import (CouchbaseException,
                                  InternalServerFailureException,
                                  QueryIndexAlreadyExistsException)
from couchbase.management.buckets import CreateBucketSettings
from couchbase.options import ClusterOptions
from datasets import load_dataset
from dotenv import load_dotenv

from langchain_couchbase.vectorstores import CouchbaseQueryVectorStore
from langchain_couchbase.vectorstores import DistanceStrategy
from langchain_couchbase.vectorstores import IndexType
from langchain_openai import OpenAIEmbeddings

from dataclasses import dataclass
from pydantic_ai import Agent, RunContext

Setup Logging

Logging is configured to track the progress of the script and capture any errors or warnings. This is crucial for debugging and understanding the flow of execution. The logging output includes timestamps, log levels (e.g., INFO, ERROR), and messages that describe what is happening in the script.

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', force=True)

# Disable all logging except critical to prevent OpenAI API request logs
logging.getLogger("httpx").setLevel(logging.CRITICAL)

Loading Sensitive Information

In this section, we prompt the user to input essential configuration settings needed. These settings include sensitive information like API keys, database credentials, and specific configuration names. Instead of hardcoding these details into the script, we request the user to provide them at runtime, ensuring flexibility and security.

The script also validates that all required inputs are provided, raising an error if any crucial information is missing. This approach ensures that your integration is both secure and correctly configured without hardcoding sensitive information, enhancing the overall security and maintainability of your code.

load_dotenv()

# Load from environment variables or prompt for input in one-liners
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY') or getpass.getpass('Enter your OpenAI API key: ')
CB_HOST = os.getenv('CB_HOST', 'couchbase://localhost') or input('Enter your Couchbase host (default: couchbase://localhost): ') or 'couchbase://localhost'
CB_USERNAME = os.getenv('CB_USERNAME', 'Administrator') or input('Enter your Couchbase username (default: Administrator): ') or 'Administrator'
CB_PASSWORD = os.getenv('CB_PASSWORD', 'password') or getpass.getpass('Enter your Couchbase password (default: password): ') or 'password'
CB_BUCKET_NAME = os.getenv('CB_BUCKET_NAME', 'query-vector-search-testing') or input('Enter your Couchbase bucket name (default: query-vector-search-testing): ') or 'query-vector-search-testing'
SCOPE_NAME = os.getenv('SCOPE_NAME', 'shared') or input('Enter your scope name (default: shared): ') or 'shared'
COLLECTION_NAME = os.getenv('COLLECTION_NAME', 'pydantic_ai') or input('Enter your collection name (default: pydantic_ai): ') or 'pydantic_ai'

# Check if the variables are correctly loaded
if not OPENAI_API_KEY:
    raise ValueError("OPENAI_API_KEY is not set in the environment.")
    
if 'OPENAI_API_KEY' not in os.environ:
    os.environ['OPENAI_API_KEY'] = OPENAI_API_KEY

Connecting to the Couchbase Cluster

Connecting to a Couchbase cluster is the foundation of our project. Couchbase will serve as our primary data store, handling all the storage and retrieval operations required for our semantic search engine. By establishing this connection, we enable our application to interact with the database, allowing us to perform operations such as storing embeddings, querying data, and managing collections. This connection is the gateway through which all data will flow, so ensuring it's set up correctly is paramount.

try:
    auth = PasswordAuthenticator(CB_USERNAME, CB_PASSWORD)
    options = ClusterOptions(auth)
    cluster = Cluster(CB_HOST, options)
    cluster.wait_until_ready(timedelta(seconds=5))
    logging.info("Successfully connected to Couchbase")
except Exception as e:
    raise ConnectionError(f"Failed to connect to Couchbase: {str(e)}")
2025-11-07 14:19:15,306 - INFO - Successfully connected to Couchbase

Setting Up Collections in Couchbase

The setup_collection() function handles creating and configuring the hierarchical data organization in Couchbase:

  1. Bucket Creation:

    • Checks if specified bucket exists, creates it if not
    • Sets bucket properties like RAM quota (1024MB) and replication (disabled)
    • Note: You will not be able to create a bucket on Capella
  2. Scope Management:

    • Verifies if requested scope exists within bucket
    • Creates new scope if needed (unless it's the default "_default" scope)
  3. Collection Setup:

    • Checks for collection existence within scope
    • Creates collection if it doesn't exist
    • Waits 2 seconds for collection to be ready

Additional Tasks:

  • Clears any existing documents for clean state
  • Implements comprehensive error handling and logging
def setup_collection(cluster, bucket_name, scope_name, collection_name):
    try:
        bucket_manager = cluster.buckets()

        # Check if bucket exists, create if it doesn't. cluster.bucket() only
        # constructs a handle, so use the management API for existence checks.
        try:
            bucket_manager.get_bucket(bucket_name)
            logging.info(f"Bucket '{bucket_name}' exists.")
        except Exception:
            logging.info(f"Bucket '{bucket_name}' does not exist. Creating it...")
            bucket_settings = CreateBucketSettings(
                name=bucket_name,
                bucket_type='couchbase',
                ram_quota_mb=1024,
                flush_enabled=True,
                num_replicas=0
            )
            bucket_manager.create_bucket(bucket_settings)
            logging.info(f"Bucket '{bucket_name}' created successfully.")

        bucket = cluster.bucket(bucket_name)
        time.sleep(5)  # Wait for bucket creation to complete and become available
        collection_manager = bucket.collections()

        # Check if scope exists, create if it doesn't
        scopes = collection_manager.get_all_scopes()
        scope_exists = any(scope.name == scope_name for scope in scopes)
        
        if not scope_exists and scope_name != "_default":
            logging.info(f"Scope '{scope_name}' does not exist. Creating it...")
            collection_manager.create_scope(scope_name)
            logging.info(f"Scope '{scope_name}' created successfully.")
            time.sleep(2)
            scopes = collection_manager.get_all_scopes()

        # Check if collection exists, create if it doesn't
        collection_exists = any(
            scope.name == scope_name and collection_name in [col.name for col in scope.collections]
            for scope in scopes
        )

        if not collection_exists:
            logging.info(f"Collection '{collection_name}' does not exist. Creating it...")
            collection_manager.create_collection(scope_name, collection_name)
            logging.info(f"Collection '{collection_name}' created successfully.")
        else:
            logging.info(f"Collection '{collection_name}' already exists. Skipping creation.")

        # Wait for collection to be ready
        collection = bucket.scope(scope_name).collection(collection_name)
        time.sleep(2)  # Give the collection time to be ready for queries

        # Clear all documents in the collection
        try:
            query = f"DELETE FROM `{bucket_name}`.`{scope_name}`.`{collection_name}`"
            cluster.query(query).execute()
            logging.info("All documents cleared from the collection.")
        except Exception as e:
            logging.warning(f"Error while clearing documents: {str(e)}. The collection might be empty.")

        return collection
    except Exception as e:
        raise RuntimeError(f"Error setting up collection: {str(e)}")
    
setup_collection(cluster, CB_BUCKET_NAME, SCOPE_NAME, COLLECTION_NAME)
2025-11-07 14:19:15,319 - INFO - Bucket 'travel-sample' exists.
2025-11-07 14:19:15,320 - INFO - Scope 'shared' does not exist. Creating it...
2025-11-07 14:19:15,352 - INFO - Scope 'shared' created successfully.
2025-11-07 14:19:15,354 - INFO - Collection 'demo' does not exist. Creating it...
2025-11-07 14:19:15,407 - INFO - Collection 'demo' created successfully.
2025-11-07 14:19:17,451 - INFO - All documents cleared from the collection.





<couchbase.collection.Collection at 0x12f2fe3c0>

Creating OpenAI Embeddings

Embeddings are at the heart of semantic search. They are numerical representations of text that capture the semantic meaning of the words and phrases. Unlike traditional keyword-based search, which looks for exact matches, embeddings allow our search engine to understand the context and nuances of language, enabling it to retrieve documents that are semantically similar to the query, even if they don't contain the exact keywords. By creating embeddings using OpenAI, we equip our search engine with the ability to understand and process natural language in a way that's much closer to how humans understand language. This step transforms our raw text data into a format that the search engine can use to find and rank relevant documents.

try:
    embeddings = OpenAIEmbeddings(openai_api_key=OPENAI_API_KEY, model='text-embedding-3-small')
    logging.info("Successfully created OpenAIEmbeddings")
except Exception as e:
    raise ValueError(f"Error creating OpenAIEmbeddings: {str(e)}")
2025-11-07 14:19:17,576 - INFO - Successfully created OpenAIEmbeddings

Understanding Hyperscale and Composite Vector Search

Optimizing Vector Search with Hyperscale and Composite Vector Index

With Couchbase 8.0+, you can leverage the power of query-based vector search, which offers significant performance improvements over traditional Full-Text Search (FTS) approaches for vector-first workloads. Hyperscale and Composite Vector Index search provides high-performance vector similarity search with advanced filtering capabilities and is designed to scale to billions of vectors.

Hyperscale/Composite vs Search Vector Index: Choosing the Right Approach

Feature Hyperscale/Composite Vector Index Search Vector Index
Best For Vector-first workloads, complex filtering, high QPS performance Hybrid search and high recall rates
Couchbase Version 8.0.0+ 7.6+
Filtering Pre-filtering with WHERE clauses (Composite) or post-filtering (Hyperscale) Pre-filtering with flexible ordering
Scalability Up to billions of vectors (Hyperscale) Up to 10 million vectors
Performance Optimized for concurrent operations with low memory footprint Good for mixed text and vector queries

Query-Based Vector Index Types

Couchbase offers two distinct query-based vector index types, each optimized for different use cases:

Hyperscale Vector Indexes
  • Best for: Pure vector searches like content discovery, recommendations, and semantic search
  • Use when: You primarily perform vector-only queries without complex scalar filtering
  • Features:
    • High performance with low memory footprint
    • Optimized for concurrent operations
    • Designed to scale to billions of vectors
    • Supports post-scan filtering for basic metadata filtering
Composite Vector Indexes
  • Best for: Filtered vector searches that combine vector similarity with scalar value filtering
  • Use when: Your queries combine vector similarity with scalar filters that eliminate large portions of data
  • Features:
    • Efficient pre-filtering where scalar attributes reduce the vector comparison scope
    • Best for well-defined workloads requiring complex filtering using Hyperscale and Composite Vector Index features
    • Supports range lookups combined with vector search

Index Type Selection for This Tutorial

In this tutorial, we'll demonstrate creating a Hyperscale index and running vector similarity queries using Hyperscale and Composite Vector Index. Hyperscale is ideal for semantic search scenarios where you want:

  1. High-performance vector search across large datasets
  2. Low latency for real-time applications
  3. Scalability to handle growing vector collections
  4. Concurrent operations for multi-user environments

The Hyperscale index will provide optimal performance for our OpenAI embedding-based semantic search implementation.

Alternative: Composite Vector Index

If your use case requires complex filtering with scalar attributes, you may want to consider using a Composite Vector Index instead:

## Alternative: Create a Composite index for filtered searches
vector_store.create_index(
    index_type=IndexType.COMPOSITE,
    index_description="IVF,SQ8",
    distance_metric=DistanceStrategy.COSINE,
    index_name="pydantic_composite_index",
)

Use Composite indexes when:

  • You need to filter by document metadata or attributes before vector similarity
  • Your queries combine vector search with WHERE clauses
  • You have well-defined filtering requirements that can reduce the search space

Note: Composite indexes enable pre-filtering with scalar attributes, making them ideal for applications where you need to search within specific categories, date ranges, or user-specific data segments.

Understanding Index Configuration (Couchbase 8.0 Feature)

Before creating our Hyperscale index, it's important to understand the configuration parameters that optimize vector storage and search performance. The index_description parameter controls how Couchbase optimizes vector storage through centroids and quantization.

Index Description Format: 'IVF[<centroids>],{PQ|SQ}<settings>'
Centroids (IVF - Inverted File)
  • Controls how the dataset is subdivided for faster searches
  • More centroids = faster search, slower training time
  • Fewer centroids = slower search, faster training time
  • If omitted (like IVF,SQ8), Couchbase auto-selects based on dataset size
Quantization Options

Scalar Quantization (SQ):

  • SQ4, SQ6, SQ8 (4, 6, or 8 bits per dimension)
  • Lower memory usage, faster search, slightly reduced accuracy

Product Quantization (PQ):

  • Format: PQ<subquantizers>x<bits> (e.g., PQ32x8)
  • Better compression for very large datasets
  • More complex but can maintain accuracy with smaller index size
Common Configuration Examples
  • IVF,SQ8 - Auto centroids, 8-bit scalar quantization (good default)
  • IVF1000,SQ6 - 1000 centroids, 6-bit scalar quantization
  • IVF,PQ32x8 - Auto centroids, 32 subquantizers with 8 bits

For detailed configuration options, see the Quantization & Centroid Settings.

For more information on query-based vector indexes, see Couchbase Vector Index Documentation.

Our Configuration Choice

In this tutorial, we use IVF,SQ8 which provides:

  • Auto-selected centroids optimized for our dataset size
  • 8-bit scalar quantization for good balance of speed, memory usage, and accuracy
  • COSINE distance metric ideal for semantic similarity search
  • Optimal performance for most semantic search use cases

Setting Up the Couchbase Query Vector Store

A vector store is where we'll keep our embeddings. The query vector store is specifically designed to handle embeddings and perform similarity searches. When a user inputs a query, the Couchbase Query Service converts the query into an embedding and compares it against the embeddings stored in the vector store. This allows the engine to find documents that are semantically similar to the query, even if they don't contain the exact same words. By setting up the vector store in Couchbase, we create a powerful tool that enables us to understand and retrieve information based on the meaning and context of the query, rather than just the specific words used.

The vector store requires a distance metric to determine how similarity between vectors is calculated. This is crucial for accurate semantic search results as different distance metrics can yield different similarity rankings. Some of the supported Distance strategies are dot, l2, euclidean, cosine, l2_squared, euclidean_squared. In our implementation we will use cosine which is particularly effective for text embeddings.

try:
    vector_store = CouchbaseQueryVectorStore(
        cluster=cluster,
        bucket_name=CB_BUCKET_NAME,
        scope_name=SCOPE_NAME,
        collection_name=COLLECTION_NAME,
        embedding = embeddings,
        distance_metric=DistanceStrategy.COSINE
    )
    logging.info("Successfully created vector store")
except Exception as e:
    raise ValueError(f"Failed to create vector store: {str(e)}")
2025-11-07 14:19:18,781 - INFO - Successfully created vector store

Load the BBC News Dataset

To build a search engine, we need data to search through. We use the BBC News dataset from RealTimeData, which provides real-world news articles. This dataset contains news articles from BBC covering various topics and time periods. Loading the dataset is a crucial step because it provides the raw material that our search engine will work with. The quality and diversity of the news articles make it an excellent choice for testing and refining our search engine, ensuring it can handle real-world news content effectively.

The BBC News dataset allows us to work with authentic news articles, enabling us to build and test a search engine that can effectively process and retrieve relevant news content. The dataset is loaded using the Hugging Face datasets library, specifically accessing the "RealTimeData/bbc_news_alltime" dataset with the "2024-12" version.

try:
    news_dataset = load_dataset(
        "RealTimeData/bbc_news_alltime", "2024-12", split="train"
    )
    print(f"Loaded the BBC News dataset with {len(news_dataset)} rows")
    logging.info(f"Successfully loaded the BBC News dataset with {len(news_dataset)} rows.")
except Exception as e:
    raise ValueError(f"Error loading the BBC News dataset: {str(e)}")
2025-11-07 14:19:23,958 - INFO - Successfully loaded the BBC News dataset with 2687 rows.


Loaded the BBC News dataset with 2687 rows

Cleaning up the Data

We will use the content of the news articles for our RAG system.

The dataset contains a few duplicate records. We are removing them to avoid duplicate results in the retrieval stage of our RAG system.

news_articles = news_dataset["content"]
unique_articles = set()
for article in news_articles:
    if article:
        unique_articles.add(article)
unique_news_articles = list(unique_articles)
print(f"We have {len(unique_news_articles)} unique articles in our database.")
We have 1749 unique articles in our database.

Saving Data to the Vector Store

To efficiently handle the large number of articles, we process them in batches of articles at a time. This batch processing approach helps manage memory usage and provides better control over the ingestion process.

We first filter out any articles that exceed 50,000 characters to avoid potential issues with token limits. Then, using the vector store's add_texts method, we add the filtered articles to our vector database. The batch_size parameter controls how many articles are processed in each iteration.

This approach offers several benefits:

  1. Memory Efficiency: Processing in smaller batches prevents memory overload
  2. Progress Tracking: Easier to monitor and track the ingestion progress
  3. Resource Management: Better control over CPU and network resource utilization

We use a conservative batch size of 100 to ensure reliable operation. The optimal batch size depends on many factors including:

  • Document sizes being inserted
  • Available system resources
  • Network conditions
  • Concurrent workload

Consider measuring performance with your specific workload before adjusting.

batch_size = 100

# Automatic Batch Processing
articles = [article for article in unique_news_articles if article and len(article) <= 50000]

try:
    vector_store.add_texts(
        texts=articles,
        batch_size=batch_size
    )
    logging.info("Document ingestion completed successfully.")
except Exception as e:
    raise ValueError(f"Failed to save documents to vector store: {str(e)}")
2025-11-07 14:20:20,820 - INFO - Document ingestion completed successfully.

Understanding Semantic Search in Couchbase

Semantic search goes beyond traditional keyword matching by understanding the meaning and context behind queries. Here's how it works in Couchbase:

How Semantic Search Works

  1. Vector Embeddings: Documents and queries are converted into high-dimensional vectors using an embeddings model (in our case, OpenAI's text-embedding-3-small)

  2. Similarity Calculation: When a query is made, Couchbase compares the query vector against stored document vectors using the COSINE distance metric

  3. Result Ranking: Documents are ranked by their vector distance (lower distance = more similar meaning)

  4. Flexible Configuration: Different distance metrics (cosine, euclidean, dot product) and embedding models can be used based on your needs

The similarity_search_with_score method performs this entire process, returning documents along with their similarity scores. This enables you to find semantically related content even when exact keywords don't match.

Now let's see semantic search in action and measure its performance with different optimization strategies.

Vector Search Performance Testing

Now let's measure and compare the performance benefits of different optimization strategies. We'll conduct a comprehensive performance analysis across two phases:

Performance Testing Phases:

  1. Phase 1 - Baseline Performance: Test vector search without Hyperscale indexes to establish baseline metrics
  2. Phase 2 - Hyperscale-Optimized Search: Create Hyperscale index and measure performance improvements

Important Context:

  • Hyperscale performance benefits scale with dataset size and concurrent load
  • With our dataset (~1,700 articles), improvements may be modest
  • Production environments with millions of vectors show significant Hyperscale advantages
  • The combination of Hyperscale + LLM caching provides optimal RAG performance

Phase 1: Baseline Performance (No Hyperscale Index)

# Phase 1: Baseline Performance (Without Hyperscale Index)
print("="*80)
print("PHASE 1: BASELINE PERFORMANCE (NO HYPERSCALE INDEX)")
print("="*80)

query = "What was manchester city manager pep guardiola's reaction to the team's current form?"

try:
    # Perform the semantic search
    start_time = time.time()
    search_results = vector_store.similarity_search_with_score(query, k=10)
    baseline_time = time.time() - start_time

    logging.info(f"Baseline search completed in {baseline_time:.2f} seconds")

    # Display search results
    print(f"\nBaseline Search Results (completed in {baseline_time:.4f} seconds):")
    print("-" * 80)
    for i, (doc, distance) in enumerate(search_results, 1):
        print(f"[Result {i}] Vector Distance: {distance:.4f}")
        # Truncate for readability
        content_preview = doc.page_content[:150] + "..." if len(doc.page_content) > 150 else doc.page_content
        print(f"Text: {content_preview}")
        print("-" * 80)

except CouchbaseException as e:
    raise RuntimeError(f"Error performing semantic search: {str(e)}")
except Exception as e:
    raise RuntimeError(f"Unexpected error: {str(e)}")
================================================================================
PHASE 1: BASELINE PERFORMANCE (NO INDEX)
================================================================================


2025-11-07 14:20:22,185 - INFO - Baseline search completed in 1.36 seconds



Baseline Search Results (completed in 1.3612 seconds):
--------------------------------------------------------------------------------
[Result 1] Vector Distance: 0.2956
Text: Manchester City boss Pep Guardiola has won 18 trophies since he arrived at the club in 2016

Manchester City boss Pep Guardiola says he is "fine" desp...
--------------------------------------------------------------------------------
[Result 2] Vector Distance: 0.3100
Text: Pep Guardiola has said Manchester City will be his final managerial job in club football before he "maybe" coaches a national team.

The former Barcel...
--------------------------------------------------------------------------------
[Result 3] Vector Distance: 0.3311
Text: 'I am not good enough' - Guardiola faces daunting and major rebuild

This video can not be played To play this video you need to enable JavaScript in ...
--------------------------------------------------------------------------------
[Result 4] Vector Distance: 0.3474
Text: 'Self-doubt, errors & big changes' - inside the crisis at Man City

Pep Guardiola has not been through a moment like this in his managerial career. Ma...
--------------------------------------------------------------------------------
[Result 5] Vector Distance: 0.3666
Text: Man City's Dias ruled out for 'three or four weeks'

Ruben Dias has won 10 major trophies during his time at Manchester City

Manchester City have suf...
--------------------------------------------------------------------------------
[Result 6] Vector Distance: 0.3818
Text: 'We have to find a way' - Guardiola vows to end relegation form

This video can not be played To play this video you need to enable JavaScript in your...
--------------------------------------------------------------------------------
[Result 7] Vector Distance: 0.4157
Text: Man City might miss out on Champions League - Guardiola

Erling Haaland was part of the Manchester City side that won the Champions League for the fir...
--------------------------------------------------------------------------------
[Result 8] Vector Distance: 0.4705
Text: 'So happy he is back' - 'integral' De Bruyne 'one of best we've seen'

This video can not be played To play this video you need to enable JavaScript i...
--------------------------------------------------------------------------------
[Result 9] Vector Distance: 0.4831
Text: 'Life is not easy' - Haaland penalty miss sums up Man City crisis

Manchester City striker Erling Haaland has now missed two of his 17 penalties taken...
--------------------------------------------------------------------------------
[Result 10] Vector Distance: 0.5382
Text: Amorim knows job in 'danger' without victories

This video can not be played To play this video you need to enable JavaScript in your browser. 'I know...
--------------------------------------------------------------------------------

Creating the Hyperscale Index

Now that we understand the different index types and configuration options (covered above), let's create a Hyperscale index for our vector store. This method takes an index type (HYPERSCALE or COMPOSITE) and description parameter for optimization settings.

vector_store.create_index(index_type=IndexType.HYPERSCALE, index_name="pydantic_ai_hyperscale_index", index_description="IVF,SQ8")

Note: To create a COMPOSITE index, the below code can be used. Choose based on your specific use case and query patterns. For this tutorial's news search scenario, either index type would work, but Hyperscale is more efficient for pure semantic search across news articles.

vector_store.create_index(index_type=IndexType.COMPOSITE, index_name="pydantic_ai_composite_index", index_description="IVF,SQ8")

Phase 2: Hyperscale-Optimized Performance

# Phase 2: Hyperscale-Optimized Performance (With Hyperscale Index)
print("\n" + "="*80)
print("PHASE 2: HYPERSCALE-OPTIMIZED PERFORMANCE (WITH HYPERSCALE INDEX)")
print("="*80)

query = "What was manchester city manager pep guardiola's reaction to the team's current form?"

try:
    # Perform the semantic search with Hyperscale index
    start_time = time.time()
    search_results = vector_store.similarity_search_with_score(query, k=3)
    gsi_time = time.time() - start_time

    logging.info(f"Hyperscale-optimized search completed in {gsi_time:.2f} seconds")

    # Display search results
    print(f"\nHyperscale-Optimized Search Results (completed in {gsi_time:.4f} seconds):")
    print("-" * 80)
    for i, (doc, distance) in enumerate(search_results, 1):
        print(f"[Result {i}] Vector Distance: {distance:.4f}")
        # Truncate for readability
        content_preview = doc.page_content[:150] + "..." if len(doc.page_content) > 150 else doc.page_content
        print(f"Text: {content_preview}")
        print("-" * 80)

except CouchbaseException as e:
    raise RuntimeError(f"Error performing semantic search: {str(e)}")
except Exception as e:
    raise RuntimeError(f"Unexpected error: {str(e)}")
================================================================================
PHASE 2: GSI-OPTIMIZED PERFORMANCE (WITH HYPERSCALE INDEX)
================================================================================


2025-11-07 14:20:28,399 - INFO - GSI-optimized search completed in 0.41 seconds



GSI-Optimized Search Results (completed in 0.4124 seconds):
--------------------------------------------------------------------------------
[Result 1] Vector Distance: 0.2956
Text: Manchester City boss Pep Guardiola has won 18 trophies since he arrived at the club in 2016

Manchester City boss Pep Guardiola says he is "fine" desp...
--------------------------------------------------------------------------------
[Result 2] Vector Distance: 0.3100
Text: Pep Guardiola has said Manchester City will be his final managerial job in club football before he "maybe" coaches a national team.

The former Barcel...
--------------------------------------------------------------------------------
[Result 3] Vector Distance: 0.3311
Text: 'I am not good enough' - Guardiola faces daunting and major rebuild

This video can not be played To play this video you need to enable JavaScript in ...
--------------------------------------------------------------------------------

Performance Analysis Summary

Let's analyze the performance improvements we've achieved through different optimization strategies:

print("\n" + "="*80)
print("VECTOR SEARCH PERFORMANCE OPTIMIZATION SUMMARY")
print("="*80)

print(f"\n📊 Performance Comparison:")
print(f"{'Optimization Level':<35} {'Time (seconds)':<20} {'Status'}")
print("-" * 80)
print(f"{'Phase 1 - Baseline (No Index)':<35} {baseline_time:.4f}{'':16} ⚪ Baseline")
print(f"{'Phase 2 - Hyperscale-Optimized':<35} {gsi_time:.4f}{'':16} ✅ Optimized")

# Calculate improvement
if baseline_time > gsi_time:
    speedup = baseline_time / gsi_time
    improvement = ((baseline_time - gsi_time) / baseline_time) * 100
    print(f"\n✨ Hyperscale Performance Gain: {speedup:.2f}x faster ({improvement:.1f}% improvement)")
elif gsi_time > baseline_time:
    slowdown_pct = ((gsi_time - baseline_time) / baseline_time) * 100
    print(f"\n⚠️  Note: Hyperscale was {slowdown_pct:.1f}% slower than baseline in this run")
    print(f"   This can happen with small datasets. Hyperscale benefits emerge with scale.")
else:
    print(f"\n⚖️  Performance: Comparable to baseline")

print("\n" + "-"*80)
print("KEY INSIGHTS:")
print("-"*80)
print("1. 🚀 Hyperscale Optimization:")
print("   • Hyperscale indexes excel with large-scale datasets (millions+ vectors)")
print("   • Performance gains increase with dataset size and concurrent queries")
print("   • Optimal for production workloads with sustained traffic patterns")

print("\n2. 📦 Dataset Size Impact:")
print(f"   • Current dataset: ~1,700 articles")
print("   • At this scale, performance differences may be minimal or variable")
print("   • Significant gains typically seen with 10M+ vectors")

print("\n3. 🎯 When to Use Hyperscale:")
print("   • Large-scale vector search applications")
print("   • High query-per-second (QPS) requirements")
print("   • Multi-user concurrent access scenarios")
print("   • Production environments requiring scalability")

print("\n" + "="*80)
================================================================================
VECTOR SEARCH PERFORMANCE OPTIMIZATION SUMMARY
================================================================================

📊 Performance Comparison:
Optimization Level                  Time (seconds)       Status
--------------------------------------------------------------------------------
Phase 1 - Baseline (No Index)       1.3612                 ⚪ Baseline
Phase 2 - Hyperscale-Optimized     0.4124                 ✅ Optimized

✨ Hyperscale Performance Gain: 3.30x faster (69.7% improvement)

--------------------------------------------------------------------------------
KEY INSIGHTS:
--------------------------------------------------------------------------------
1. 🚀 Hyperscale Optimization:
   • Hyperscale indexes excel with large-scale datasets (millions+ vectors)
   • Performance gains increase with dataset size and concurrent queries
   • Optimal for production workloads with sustained traffic patterns

2. 📦 Dataset Size Impact:
   • Current dataset: ~1,700 articles
   • At this scale, performance differences may be minimal or variable
   • Significant gains typically seen with 10M+ vectors

3. 🎯 When to Use Hyperscale:
   • Large-scale vector search applications
   • High query-per-second (QPS) requirements
   • Multi-user concurrent access scenarios
   • Production environments requiring scalability

================================================================================

PydanticAI: An Introduction

From PydanticAI's website:

PydanticAI is a Python agent framework designed to make it less painful to build production grade applications with Generative AI.

PydanticAI allows us to define agents and tools easily to create Gen-AI apps in an innovative and painless manner. Some of its features are:

  • Built by the Pydantic Team: Built by the team behind Pydantic (the validation layer of the OpenAI SDK, the Anthropic SDK, LangChain, LlamaIndex, AutoGPT, Transformers, CrewAI, Instructor and many more).

  • Model-agnostic: Supports OpenAI, Anthropic, Gemini, Deepseek, Ollama, Groq, Cohere, and Mistral, and there is a simple interface to implement support for other models.

  • Type-safe: Designed to make type checking as powerful and informative as possible for you.

  • Python-centric Design: Leverages Python's familiar control flow and agent composition to build your AI-driven projects, making it easy to apply standard Python best practices you'd use in any other (non-AI) project.

  • Structured Responses: Harnesses the power of Pydantic to validate and structure model outputs, ensuring responses are consistent across runs.

  • Dependency Injection System: Offers an optional dependency injection system to provide data and services to your agent's system prompts, tools and result validators. This is useful for testing and eval-driven iterative development.

  • Streamed Responses: Provides the ability to stream LLM outputs continuously, with immediate validation, ensuring rapid and accurate results.

  • Graph Support: Pydantic Graph provides a powerful way to define graphs using typing hints, this is useful in complex applications where standard control flow can degrade to spaghetti code.

Building a RAG Agent using PydanticAI

PydanticAI makes heavy use of dependency injection to provide data and services to your agent's system prompts and tools. We define dependencies using a dataclass, which serves as a container for our dependencies.

In our case, the only dependency for our agent to work is the CouchbaseQueryVectorStore instance. However, we will still use a dataclass as it is good practice. In the future, in case we wish to add more dependencies, we can just add more fields to the dataclass Deps.

We also initialize an agent as a GPT-4o model. PydanticAI supports many different LLM providers, including Anthropic, Google, Cohere, etc. which can also be used. While initializing the agent, we also pass the type of the dependencies. This is mainly used for type checking, and not actually used at runtime.

@dataclass
class Deps:
    vector_store: CouchbaseQueryVectorStore

agent = Agent("openai:gpt-4o", deps_type=Deps)

Defining the Vector Store as a Tool

PydanticAI has the concept of function tools, which are functions that can be called by LLMs to retrieve extra information that can help form a better response.

We can perform RAG by creating a tool which retrieves documents that are semantically similar to the query, and allowing the agent to call the tool when required. We can add the function as a tool using the @agent.tool decorator.

Notice that we also add the context parameter, which contains the dependencies that are passed to the tool (in this case, the only dependency is the vector store).

@agent.tool
async def retrieve(context: RunContext[Deps], search_query: str) -> str:
    """Retrieve news data based on a search query.

    Args:
        context: The call context
        search_query: The search query
    """
    search_results = context.deps.vector_store.similarity_search_with_score(search_query, k=5)
    return "\n\n".join(
        f"# Documents:\n{doc.page_content}"
        for doc, score in search_results
    )

Finally, we create a function that allows us to define our dependencies and run our agent.

async def run_agent(question: str):
    deps = Deps(
        vector_store=vector_store,
    )
    answer = await agent.run(question, deps=deps)
    return answer

Running our Agent

We have now finished setting up our vector store and agent! The system is now ready to accept queries.

query = "What was manchester city manager pep guardiola's reaction to the team's current form?"
output = await run_agent(query)

print("=" * 20, "Agent Output", "=" * 20)
print(output.output)
==================== Agent Output ====================
Pep Guardiola, Manchester City's manager, has expressed concern over the team's current form, describing it as one of the worst runs of results in his managerial career. Guardiola has admitted that the downturn in form has personally affected him, leading to poor sleep and a lighter diet due to stress. Manchester City have won just once in their past ten games, with their Champions League prospects uncertain following a defeat to Juventus. 

Guardiola has been self-critical, stating, "I am not good enough," and emphasizing his responsibility to find solutions. He acknowledged the team's defensive issues and vulnerabilities, which have been exploited by opponents, leading to losses even in games where City initially led. Despite the challenges, Guardiola remains supported by the club and its fans, who continue to reassess their situation as they aim to return to form.

Internally, Guardiola has been contemplating the reasons behind the team's struggles, which include injuries to key players and errors from usually reliable performers. He recognizes the need for a rebuilding phase and suggests that major changes, possibly including player departures, may be necessary to restore City's competitive edge.

Inspecting the Agent

We can use the all_messages() method in the output object to observe how the agent and tools work.

In the cell below, we see an extremely detailed list of all the model's messages and tool calls, which happens step by step:

  1. The UserPromptPart, which consists of the query the user sends to the agent.
  2. The agent calls the retrieve tool in the ToolCallPart message. This includes the search_query argument. Couchbase uses this search_query to perform semantic search over all the ingested news articles.
  3. The retrieve tool returns a ToolReturnPart object with all the context required for the model to answer the user's query. The retrieved documents were truncated, because a large amount of context was retrieved.
  4. The final message is the LLM generated response with the added context, which is sent back to the user.
from pprint import pprint

for idx, message in enumerate(output.all_messages(), start=1):
    print(f"Step {idx}:")
    pprint(message.__repr__())
    print("=" * 50)
Step 1:
('ModelRequest(parts=[UserPromptPart(content="What was manchester city manager '
 'pep guardiola\'s reaction to the team\'s current form?", '
 'timestamp=datetime.datetime(2025, 11, 7, 8, 50, 28, 431572, '
 "tzinfo=datetime.timezone.utc), part_kind='user-prompt')], instructions=None, "
 "kind='request')")
==================================================
Step 2:
("ModelResponse(parts=[ToolCallPart(tool_name='retrieve', "
 'args=\'{"search_query":"Manchester City Pep Guardiola reaction to current '
 'form"}\', tool_call_id=\'call_0iM6cTSBayIc2ypx0HhJgNn0\', '
 "part_kind='tool-call')], model_name='gpt-4o-2024-08-06', "
 'timestamp=datetime.datetime(2025, 11, 7, 8, 50, 28, '
 "tzinfo=datetime.timezone.utc), kind='response')")
==================================================
Step 3:
("ModelRequest(parts=[ToolReturnPart(tool_name='retrieve', content='# "
 'Documents:\\nManchester City boss Pep Guardiola has won 18 trophies since he '
 'arrived at the club in 2016\\n\\nManchester City boss Pep Guardiola says he '
 'is "fine" despite admitting his sleep and diet are being affected by the '
 'worst run of results in his entire managerial career. In an interview with '
 'former Italy international Luca Toni for Amazon Prime Sport before '
 "Wednesday\\'s Champions League defeat by Juventus, Guardiola touched on the "
 "personal impact City\\'s sudden downturn in form has had. Guardiola said his "
 'state of mind was "ugly", that his sleep was "worse" and he was eating '
 "lighter as his digestion had suffered. City go into Sunday\\'s derby against "
 'Manchester United at Etihad Stadium having won just one of their past 10 '
 'games. The Juventus loss means there is a chance they may not even secure a '
 'play-off spot in the Champions League. Asked to elaborate on his comments to '
 'Toni, Guardiola said: "I\\\'m fine. "In our jobs we always want to do our '
 "best or the best as possible. When that doesn\\'t happen you are more "
 'uncomfortable than when the situation is going well, always that happened. '
 '"In good moments I am happier but when I get to the next game I am still '
 'concerned about what I have to do. There is no human being that makes an '
 'activity and it doesn\\\'t matter how they do." Guardiola said City have to '
 'defend better and "avoid making mistakes at both ends". To emphasise his '
 "point, Guardiola referred back to the third game of City\\'s current run, "
 'against a Sporting side managed by Ruben Amorim, who will be in the United '
 'dugout at the weekend. City dominated the first half in Lisbon, led thanks '
 "to Phil Foden\\'s early effort and looked to be cruising. Instead, they "
 'conceded three times in 11 minutes either side of half-time as Sporting '
 'eventually ran out 4-1 winners. "I would like to play the game like we '
 'played in Lisbon on Sunday, believe me," said Guardiola, who is facing the '
 'prospect of only having three fit defenders for the derby as Nathan Ake and '
 'Manuel Akanji try to overcome injury concerns. If there is solace for City, '
 'it comes from the knowledge United are not exactly flying. Their comeback '
 'Europa League victory against Viktoria Plzen on Thursday was their third win '
 "of Amorim\\'s short reign so far but only one of those successes has come in "
 'the Premier League, where United have lost their past two games against '
 'Arsenal and Nottingham Forest. Nevertheless, Guardiola can see improvements '
 'already on the red side of the city. "It\\\'s already there," he said. "You '
 'see all the patterns, the movements, the runners and the pace. He will do a '
 'good job at United, I\\\'m pretty sure of that."\\n\\nGuardiola says skipper '
 'Kyle Walker has been offered support by the club after the City defender '
 'highlighted the racial abuse he had received on social media in the wake of '
 'the Juventus trip. "It\\\'s unacceptable," he said. "Not because it\\\'s '
 'Kyle - for any human being. "Unfortunately it happens many times in the real '
 'world. It is not necessary to say he has the support of the entire club. It '
 'is completely unacceptable and we give our support to him."\\n\\n# '
 "Documents:\\n\\'I am not good enough\\' - Guardiola faces daunting and major "
 'rebuild\\n\\nThis video can not be played To play this video you need to '
 "enable JavaScript in your browser. \\'I am not good enough\\' - Guardiola "
 "says he must find a \\'solution\\' after derby loss\\n\\nPep Guardiola says "
 "his sleep has suffered during Manchester City\\'s deepening crisis, so he "
 'will not be helped by a nightmarish conclusion to one of the most stunning '
 'defeats of his long reign. Guardiola looked agitated, animated and on edge '
 "even after City led the Manchester derby through Josko Gvardiol\\'s "
 '36th-minute header, his reaction to the goal one of almost disdain that it '
 'came via a deflected cross as opposed to in his purist style. He sat alone '
 'with his eyes closed sipping from a water bottle before the resumption of '
 'the second half, then was denied even the respite of victory when Manchester '
 'United gave this largely dismal derby a dramatic conclusion it barely '
 'deserved with a remarkable late comeback. First, with 88 minutes on the '
 'clock, Matheus Nunes presented Amad Diallo with the ball before compounding '
 'his error by flattening the forward as he made an attempt to recover his '
 'mistake. Bruno Fernandes completed the formalities from the penalty spot. '
 "Worse was to come two minutes later when Lisandro Martinez\\'s routine long "
 "ball caught City\\'s defence inexplicably statuesque. Goalkeeper Ederson\\'s "
 'positioning was awry, allowing the lively Diallo to pounce from an acute '
 'angle to leave Guardiola and his players stunned. It was the latest into any '
 'game, 88 minutes, that reigning Premier League champions had led then lost. '
 'It was also the first time City had lost a game they were leading so late '
 "on. And in a sign of City\\'s previous excellence that is now being "
 'challenged, they have only lost four of 105 Premier League home games under '
 'Guardiola in which they have been ahead at half-time, winning 94 and drawing '
 'seven. Guardiola delivered a brutal self-analysis as he told Match of the '
 'Day: "I am not good enough. I am the boss. I am the manager. I have to find '
 'solutions and so far I haven\\\'t. That\\\'s the reality. "Not much else to '
 'say. No defence. Manchester United were incredibly persistent. We have not '
 'lost eight games in two seasons. We can\\\'t defend that."\\n\\nManchester '
 'City manager Pep Guardiola in despair during the derby defeat to Manchester '
 'United\\n\\nGuardiola suggested the serious renewal will wait until the '
 'summer but the red flags have been appearing for weeks in the sudden and '
 'shocking decline of a team that has lost the aura of invincibility that left '
 'many opponents beaten before kick-off in previous years. He has had stated '
 'City must "survive" this season - whatever qualifies as survival for a club '
 'of such rich ambition - but the quest for a record fifth successive Premier '
 'League title is surely over as they lie nine points behind leaders Liverpool '
 'having played a game more. Their Champions League aspirations are also in '
 "jeopardy after another loss, this time against Juventus in Turin. City\\'s "
 'squad has been allowed to grow too old together. The insatiable thirst for '
 'success seems to have gone, the scales of superiority have fallen away and '
 'opponents now sense vulnerability right until the final whistle, as United '
 'did here. The manner in which United were able, and felt able, to snatch '
 'this victory drove right to the heart of how City, and Guardiola, are '
 'allowing opponents to prey on their downfall. Guardiola has every reason to '
 'cite injuries, most significantly to Rodri and also John Stones as well as '
 'others, but this cannot be used an excuse for such a dramatic decline in '
 'standards, allied to the appearance of a soft underbelly that is so easily '
 "exploited. And City\\'s rebuild will not be a quick fix. With every "
 'performance, every defeat, the scale of what lies in front of Guardiola '
 "becomes more obvious - and daunting. Manchester City\\'s fans did their best "
 'to reassure Guardiola of their faith in him with a giant Barcelona-inspired '
 'banner draped from the stands before kick-off emblazoned with his image '
 'reading "Més que un entrenador" - "More Than A Coach". And Guardiola will '
 'now need to be more than a coach than at any time in his career. He will '
 "have the finances but it will be done with City\\'s challengers also "
 'strengthening. Kevin de Bruyne, 34 in June, lasted 68 minutes here before he '
 'was substituted. Age and injuries are catching up with one of the greatest '
 'players of the Premier League era and he is unlikely to be at City next '
 'season. Mateo Kovacic, who replaced De Bruyne, is also 31 in May. Kyle '
 'Walker, 34, is being increasingly exposed. His most notable contribution '
 'here was an embarrassing collapse to the ground after the mildest '
 'head-to-head collision with Rasmus Hojlund. Ilkay Gundogan, another '
 "34-year-old and a previous pillar of Guardiola\\'s great successes, no "
 'longer has the legs or energy to exert influence. This looks increasingly '
 'like a season too far following his return from Barcelona. Flaws are also '
 'being exposed elsewhere, with previously reliable performers failing to hit '
 'previous standards. Phil Foden scored 27 goals and had 12 assists when he '
 'was Premier League Player of the Season last term. This year he has just '
 'three goals and two assists in 18 appearances in all competitions. He has no '
 'goals and just one assist in 11 Premier League games. Jack Grealish, who '
 'came on after 77 minutes against United, has not scored in a year for '
 'Manchester City, his last goal coming in a 2-2 draw against Crystal Palace '
 'on 16 December last year. He has, in the meantime, scored twice for England. '
 'Erling Haaland is also struggling as City lack creativity and cutting edge. '
 'He has three goals in his past 11 Premier League games after scoring 10 in '
 "his first five. And in another indication of City\\'s impotence, and their "
 "reliance on Haaland, defender Gvardiol\\'s goal against United was his "
 'fourth this season, making him their second highest scorer in all '
 'competitions behind the Norwegian striker, who has 18. Goalkeeper Ederson, '
 'so reliable for so long, has already been dropped once this season and did '
 "not cover himself in glory for United\\'s winner. Guardiola, with that "
 'freshly signed two-year contract, insists he "wants it" as he treads on this '
 'alien territory of failure. He will be under no illusions about the size of '
 'the job in front of him as he placed his head in his hands in anguish after '
 'yet another damaging and deeply revealing defeat. City and Guardiola are in '
 'new, unforgiving territory.\\n\\n# Documents:\\nPep Guardiola has said '
 'Manchester City will be his final managerial job in club football before he '
 '"maybe" coaches a national team.\\n\\nThe former Barcelona and Bayern Munich '
 'boss has won 15 major trophies since taking charge of City in 2016.\\n\\nThe '
 '53-year-old Spaniard was approached in the summer about the possibility of '
 'becoming England manager, but last month signed a two-year contract '
 'extension with City until 2027.\\n\\nSpeaking to celebrity chef Dani Garcia '
 'on YouTube, Guardiola did not indicate when he intends to step down at City '
 'but said he would not return to club football - in the Premier League or '
 'overseas.\\n\\n"I\\\'m not going to manage another team," he '
 'said.\\n\\n"I\\\'m not talking about the long-term future, but what I\\\'m '
 'not going to do is leave Manchester City, go to another country, and do the '
 'same thing as now.\\n\\n"I wouldn\\\'t have the energy. The thought of '
 'starting somewhere else, all the process of training and so on. No, no, no. '
 'Maybe a national team, but that\\\'s different.\\n\\n"I want to leave it and '
 "go and play golf, but I can\\'t [if he takes a club job]. I think stopping "
 'would do me good."\\n\\nCity have won just once since Guardiola extended his '
 'contract - and once in nine games since beating Southampton on 26 '
 'October.\\n\\nThat victory came at home to Nottingham Forest last Wednesday, '
 'but was followed by a 2-2 draw at Crystal Palace at the weekend.\\n\\nThe '
 'Blues visit Juventus next in the Champions League on Wednesday (20:00 GMT), '
 'before hosting Manchester United in the Premier League on Sunday '
 '(16:30).\\n\\n"Right now we are not in the position - when we have had the '
 'results of the last seven, eight games - to talk about winning games in '
 'plural," said Guardiola at his pre-match news conference.\\n\\n"We have to '
 'win the game and not look at what happens in the next one yet."\\n\\n# '
 "Documents:\\n\\'We have to find a way\\' - Guardiola vows to end relegation "
 'form\\n\\nThis video can not be played To play this video you need to enable '
 "JavaScript in your browser. \\'Worrying\\' and \\'staggering\\' - Why do "
 'Manchester City keep conceding?\\n\\nManchester City are currently in '
 "relegation form and there is little sign of it ending. Saturday\\'s 2-1 "
 'defeat at Aston Villa left them joint bottom of the form table over the past '
 'eight games with just Southampton for company. Saints, at the foot of the '
 'Premier League, have the same number of points, four, as City over their '
 'past eight matches having won one, drawn one and lost six - the same record '
 'as the floundering champions. And if Southampton - who appointed Ivan Juric '
 'as their new manager on Saturday - get at least a point at Fulham on Sunday, '
 'City will be on the worst run in the division. Even Wolves, who sacked boss '
 "Gary O\\'Neil last Sunday and replaced him with Vitor Pereira, have earned "
 'double the number of points during the same period having played a game '
 'fewer. They are damning statistics for Pep Guardiola, even if he does have '
 'some mitigating circumstances with injuries to Ederson, Nathan Ake and Ruben '
 'Dias - who all missed the loss at Villa Park - and the long-term loss of '
 "midfield powerhouse Rodri. Guardiola was happy with Saturday\\'s "
 'performance, despite defeat in Birmingham, but there is little solace to '
 'take at slipping further out of the title race. He may have needed to field '
 'a half-fit Manuel Akanji and John Stones at Villa Park but that does not '
 'account for City looking a shadow of their former selves. That does not '
 'justify the error Josko Gvardiol made to gift Jhon Duran a golden chance '
 'inside the first 20 seconds, or £100m man Jack Grealish again failing to '
 "have an impact on a game. There may be legitimate reasons for City\\'s drop "
 'off, whether that be injuries, mental fatigue or just simply a team coming '
 'to the end of its lifecycle, but their form, which has plunged off a cliff '
 'edge, would have been unthinkable as they strolled to a fourth straight '
 'title last season. "The worrying thing is the number of goals conceded," '
 'said ex-England captain Alan Shearer on BBC Match of the Day. "The number of '
 'times they were opened up because of the lack of protection and legs in '
 'midfield was staggering. There are so many things that are wrong at this '
 'moment in time."\\n\\nThis video can not be played To play this video you '
 "need to enable JavaScript in your browser. Man City \\'have to find a way\\' "
 'to return to form - Guardiola\\n\\nAfterwards Guardiola was calm, so much so '
 'it was difficult to hear him in the news conference, a contrast to the '
 'frustrated figure he cut on the touchline. He said: "It depends on us. The '
 'solution is bring the players back. We have just one central defender fit, '
 'that is difficult. We are going to try next game - another opportunity and '
 'we don\\\'t think much further than that. "Of course there are more reasons. '
 "We concede the goals we don\\'t concede in the past, we [don\\'t] score the "
 'goals we score in the past. Football is not just one reason. There are a lot '
 'of little factors. "Last season we won the Premier League, but we came here '
 'and lost. We have to think positive and I have incredible trust in the guys. '
 'Some of them have incredible pride and desire to do it. We have to find a '
 'way, step by step, sooner or later to find a way back." Villa boss Unai '
 "Emery highlighted City\\'s frailties, saying he felt Villa could seize on "
 'the visitors\\\' lack of belief. "Manchester City are a little bit under the '
 'confidence they have normally," he said. "The second half was different, we '
 'dominated and we scored. Through those circumstances they were feeling worse '
 'than even in the first half."\\n\\nErling Haaland had one touch in the Villa '
 'box\\n\\nThere are chinks in the armour never seen before at City under '
 'Guardiola and Erling Haaland conceded belief within the squad is low. He '
 'told TNT after the game: "Of course, [confidence levels are] not the best. '
 'We know how important confidence is and you can see that it affects every '
 'human being. That is how it is, we have to continue and stay positive even '
 'though it is difficult." Haaland, with 76 goals in 83 Premier League '
 'appearances since joining City from Borussia Dortmund in 2022, had one shot '
 'and one touch in the Villa box. His 18 touches in the whole game were the '
 'lowest of all starting players and he has been self critical, despite '
 "scoring 13 goals in the top flight this season. Over City\\'s last eight "
 'games he has netted just twice though, but Guardiola refused to criticise '
 'his star striker. He said: "Without him we will be even worse but I like the '
 "players feeling that way. I don\\'t agree with Erling. He needs to have the "
 'balls delivered in the right spots but he will fight for the next '
 'one."\\n\\n# Documents:\\n\\\'Self-doubt, errors & big changes\\\' - inside '
 'the crisis at Man City\\n\\nPep Guardiola has not been through a moment like '
 'this in his managerial career. Manchester City have lost nine matches in '
 'their past 12 - as many defeats as they had suffered in their previous 106 '
 'fixtures. At the end of October, City were still unbeaten at the top of the '
 'Premier League and favourites to win a fifth successive title. Now they are '
 'seventh, 12 points behind leaders Liverpool having played a game more. It '
 'has been an incredible fall from grace and left people trying to work out '
 'what has happened - and whether Guardiola can make it right. After '
 'discussing the situation with those who know him best, I have taken a closer '
 'look at the future - both short and long term - and how the current crisis '
 "at Man City is going to be solved.\\n\\nPep Guardiola\\'s Man City have lost "
 'nine of their past 12 matches\\n\\nGuardiola has also been giving it a lot '
 'of thought. He has not been sleeping very well, as he has said, and has not '
 'been himself at times when talking to the media. He has been talking to a '
 'lot of people about what is going on as he tries to work out the reasons for '
 "City\\'s demise. Some reasons he knows, others he still doesn\\'t. What "
 'people perhaps do not realise is Guardiola hugely doubts himself and always '
 'has. He will be thinking "I\\\'m not going to be able to get us out of this" '
 'and needs the support of people close to him to push away those insecurities '
 '- and he has that. He is protected by his people who are very aware, like he '
 'is, that there are a lot of people that want City to fail. It has been a '
 'turbulent time for Guardiola. Remember those marks he had on his head after '
 'the 3-3 draw with Feyenoord in the Champions League? He always scratches his '
 'head, it is a gesture of nervousness. Normally nothing happens but on that '
 'day one of his nails was far too sharp so, after talking to the players in '
 'the changing room where he scratched his head because of his usual agitated '
 'gesturing, he went to the news conference. His right-hand man Manel Estiarte '
 'sent him photos in a message saying "what have you got on your head?", but '
 'by the time Guardiola returned to the coaching room there was hardly '
 'anything there again. He started that day with a cover on his nose after the '
 'same thing happened at the training ground the day before. Guardiola was '
 'having a footballing debate with Kyle Walker about positional stuff and '
 'marked his nose with that same nail. There was also that remarkable news '
 'conference after the Manchester derby when he said "I don\\\'t know what to '
 'do". That is partly true and partly not true. Ignore the fact Guardiola '
 'suggested he was "not good enough". He actually meant he was not good enough '
 'to resolve the situation with the group of players he has available and with '
 'all the other current difficulties. There are obviously logical explanations '
 'for the crisis and the first one has been talked about many times - the '
 'absence of injured midfielder Rodri. You know the game Jenga? When you take '
 'the wrong piece out, the whole tower collapses. That is what has happened '
 'here. It is normal for teams to have an over-reliance on one player if he is '
 'the best in the world in his position. And you cannot calculate the '
 'consequences of an injury that rules someone like Rodri out for the season. '
 'City are a team, like many modern ones, in which the holding midfielder is a '
 'key element to the construction. So, when you take Rodri out, it is '
 'difficult to hold it together. There were Plan Bs - John Stones, Manuel '
 'Akanji, even Nathan Ake - but injuries struck. The big injury list has been '
 'out of the ordinary and the busy calendar has also played a part in '
 'compounding the issues. However, one factor even Guardiola cannot explain is '
 'the big uncharacteristic errors in almost every game from international '
 'players. Why did Matheus Nunes make that challenge to give away the penalty '
 'against Manchester United? Jack Grealish is sent on at the end to keep the '
 'ball and cannot do that. There are errors from Walker and other defenders. '
 "These are some of the best players in the world. Of course the players\\' "
 'mindset is important, and confidence is diminishing. Wrong decisions get '
 'taken so there is almost panic on the pitch instead of calm. There are also '
 'players badly out of form who are having to play because of injuries. Walker '
 "is now unable to hide behind his pace, I\\'m not sure Kevin de Bruyne is "
 'ever getting back to the level he used to be at, Bernardo Silva and Ilkay '
 'Gundogan do not have time to rest, Grealish is not playing at his best. Some '
 'of these players were only meant to be playing one game a week but, because '
 'of injuries, have played 12 games in 40 days. It all has a domino effect. '
 "One consequence is that Erling Haaland isn\\'t getting the service to score. "
 "But the Norwegian still remains City\\'s top-scorer with 13. Defender Josko "
 'Gvardiol is next on the list with just four. The way their form has been '
 'analysed inside the City camp is there have only been three games where they '
 'deserved to lose (Liverpool, Bournemouth and Aston Villa). But of course it '
 'is time to change the dynamic.\\n\\nGuardiola has never protected his '
 'players so much. He has not criticised them and is not going to do so. They '
 'have won everything with him. Instead of doing more with them, he has tried '
 'doing less. He has sometimes given them more days off to clear their heads, '
 'so they can reset - two days this week for instance. Perhaps the time to '
 'change a team is when you are winning, but no-one was suggesting Man City '
 'were about to collapse when they were top and unbeaten after nine league '
 'games. Some people have asked how bad it has to get before City make a '
 'decision on Guardiola. The answer is that there is no decision to be made. '
 'Maybe if this was Real Madrid, Barcelona or Juventus, the pressure from '
 'outside would be massive and the argument would be made that Guardiola has '
 'to go. At City he has won the lot, so how can anyone say he is failing? Yes, '
 "this is a crisis. But given all their problems, City\\'s renewed target is "
 'finishing in the top four. That is what is in all their heads now. The idea '
 'is to recover their essence by improving defensive concepts that are not '
 'there and re-establishing the intensity they are known for. Guardiola is '
 'planning to use the next two years of his contract, which is expected to be '
 'his last as a club manager, to prepare a new Manchester City. When he was at '
 'the end of his four years at Barcelona, he asked two managers what to do '
 'when you feel people are not responding to your instructions. Do you go or '
 'do the players go? Sir Alex Ferguson and Rafael Benitez both told him that '
 'the players need to go. Guardiola did not listen because of his emotional '
 'attachment to his players back then and he decided to leave the Camp Nou '
 'because he felt the cycle was over. He will still protect his players now '
 'but there is not the same emotional attachment - so it is the players who '
 'are going to leave this time. It is likely City will look to replace five or '
 'six regular starters. Guardiola knows it is the end of an era and the start '
 'of a new one. Changes will not be immediate and the majority of the work '
 'will be done in the summer. But they are open to any opportunities in '
 'January - and a holding midfielder is one thing they need. In the summer '
 "City might want to get Spain\\'s Martin Zubimendi from Real Sociedad and "
 'they know 60m euros (£50m) will get him. He said no to Liverpool last summer '
 'even though everything was agreed, but he now wants to move on and the '
 'Premier League is the target. Even if they do not get Zubimendi, that is the '
 'calibre of footballer they are after. A new Manchester City is on its way - '
 'with changes driven by Guardiola, incoming sporting director Hugo Viana and '
 "the football department.', tool_call_id='call_0iM6cTSBayIc2ypx0HhJgNn0', "
 'timestamp=datetime.datetime(2025, 11, 7, 8, 50, 30, 639657, '
 "tzinfo=datetime.timezone.utc), part_kind='tool-return')], instructions=None, "
 "kind='request')")
==================================================
Step 4:
("ModelResponse(parts=[TextPart(content='Pep Guardiola, Manchester City\\'s "
 "manager, has expressed concern over the team\\'s current form, describing it "
 'as one of the worst runs of results in his managerial career. Guardiola has '
 'admitted that the downturn in form has personally affected him, leading to '
 'poor sleep and a lighter diet due to stress. Manchester City have won just '
 'once in their past ten games, with their Champions League prospects '
 'uncertain following a defeat to Juventus. \\n\\nGuardiola has been '
 'self-critical, stating, "I am not good enough," and emphasizing his '
 "responsibility to find solutions. He acknowledged the team\\'s defensive "
 'issues and vulnerabilities, which have been exploited by opponents, leading '
 'to losses even in games where City initially led. Despite the challenges, '
 'Guardiola remains supported by the club and its fans, who continue to '
 'reassess their situation as they aim to return to form.\\n\\nInternally, '
 "Guardiola has been contemplating the reasons behind the team\\'s struggles, "
 'which include injuries to key players and errors from usually reliable '
 'performers. He recognizes the need for a rebuilding phase and suggests that '
 'major changes, possibly including player departures, may be necessary to '
 "restore City\\'s competitive edge.', part_kind='text')], "
 "model_name='gpt-4o-2024-08-06', timestamp=datetime.datetime(2025, 11, 7, 8, "
 "50, 31, tzinfo=datetime.timezone.utc), kind='response')")
==================================================

Conclusion

You've built a high-performance semantic search engine using Couchbase Hyperscale/Composite indexes with PydanticAI and LangChain. For the Search Vector Index alternative, see the search_based tutorial.


This tutorial is part of a Couchbase Learning Path:
Contents
Couchbase home page link

3155 Olsen Drive
Suite 150, San Jose
CA 95117, United States

  • Company
  • About
  • Leadership
  • News & Press
  • Careers
  • Events
  • Legal
  • Contact us
  • Support
  • Developer Portal
  • Documentation
  • Forums
  • Professional Services
  • Support Login
  • Support Policy
  • Training
  • Quicklinks
  • Blog
  • Downloads
  • Online Training
  • Resources
  • Why NoSQL
  • Pricing
  • Follow us
  • Social Media Link for TwitterTwitter
  • Social Media Link for LinkedInLinkedIn
  • Social Media Link for YoutubeYouTube
  • Social Media Link for FacebookFacebook
  • Social Media Link for GitHubGitHub
  • Social Media Link for Stack OverflowStack Overflow
  • Social Media Link for DiscordDiscord

© 2026 Couchbase, Inc. Couchbase and the Couchbase logo are registered trademarks of Couchbase, Inc. All third party trademarks (including logos and icons) referenced by Couchbase, Inc. remain the property of their respective owners.

  • Terms of Use
  • Privacy Policy
  • Cookie Policy
  • Support Policy
  • Do Not Sell My Personal Information
  • Marketing Preference Center
  • Trust Center