<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lin Liang</title>
    <description>The latest articles on DEV Community by Lin Liang (@lin_liang_7f755b0dc2fe65b).</description>
    <link>https://dev.to/lin_liang_7f755b0dc2fe65b</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2795781%2Fa52103fa-16ee-45be-92f2-c38fcd4504f0.png</url>
      <title>DEV Community: Lin Liang</title>
      <link>https://dev.to/lin_liang_7f755b0dc2fe65b</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lin_liang_7f755b0dc2fe65b"/>
    <language>en</language>
    <item>
      <title>Build Your AI-Powered Second Brain</title>
      <dc:creator>Lin Liang</dc:creator>
      <pubDate>Tue, 27 Jan 2026 08:07:07 +0000</pubDate>
      <link>https://dev.to/lin_liang_7f755b0dc2fe65b/build-your-ai-powered-second-brain-4n59</link>
      <guid>https://dev.to/lin_liang_7f755b0dc2fe65b/build-your-ai-powered-second-brain-4n59</guid>
      <description>&lt;p&gt;Build an AI-powered second brain to streamline your content creation workflow using Obsidian and GitHub Copilot CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;As an AI content creator, you need a system that captures, organizes, and enhances your ideas efficiently. By integrating Obsidian—a Markdown-based knowledge management app—with GitHub Copilot CLI, you automate note editing, rewriting, and organization using natural language prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;code&gt;Obsidian&lt;/code&gt; and set up your Vault (a folder for your notes).&lt;/li&gt;
&lt;li&gt;Install &lt;code&gt;GitHub Copilot CLI&lt;/code&gt; and authenticate with your GitHub account.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Open Your Obsidian Vault
&lt;/h2&gt;

&lt;p&gt;Open your terminal and navigate to your Vault directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/your/obsidian-vault
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Launch Copilot CLI
&lt;/h2&gt;

&lt;p&gt;Start Copilot CLI in your Vault directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;copilot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grant Copilot CLI permission to read and modify your notes when prompted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76n59v8nwcfw3v5ikhd2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76n59v8nwcfw3v5ikhd2.png" alt="Copilot CLI Interactive Session" width="782" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Automate Content Workflows
&lt;/h2&gt;

&lt;p&gt;Use natural language prompts to manage and enhance your notes. For example, to rewrite a draft for an AI content creator audience, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Act as a Senior Technical Writer. Rewrite the file &lt;span class="s1"&gt;'draft_note.md'&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;an audience of AI Content Creators.

Constraints:
- Use the Google Developer Documentation Style Guide &lt;span class="o"&gt;(&lt;/span&gt;active voice, present tense&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
- Convert all passive sentences to active.
- Remove fluff and redundant adjectives.
- If the note contains code, verify that the explanation matches the logic.

Format:
1. One-sentence summary at the top.
2. Structured Markdown with H2 and H3 headers.
3. A &lt;span class="s1"&gt;'Key Takeaways'&lt;/span&gt; bulleted list at the end.

Return the full rewritten content.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiixkvuj1amf5h0grjfg4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiixkvuj1amf5h0grjfg4.png" alt="Modify your notes" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Select a Relevant Cover Image
&lt;/h2&gt;

&lt;p&gt;Ask Copilot CLI to choose a cover image that matches your note’s topic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Explore the @[DIR] templates folder and choose the most appropriate image as the cover &lt;span class="k"&gt;for &lt;/span&gt;the note @Build Your AI-Powered Second Brain.md, based on the content. Then please put the selected cover on top of this note
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copilot will select and insert the best image for your note’s theme.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F175jiwvgu53jmzpx5zqs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F175jiwvgu53jmzpx5zqs.png" alt="Select a cover image for your note" width="800" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Integrate Obsidian and Copilot CLI to automate and enhance your content creation workflow.&lt;/li&gt;
&lt;li&gt;Use natural language prompts to rewrite, organize, and manage notes efficiently.&lt;/li&gt;
&lt;li&gt;Let Copilot CLI select relevant cover images to align your notes with their topics visually.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>cli</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>Beyond Routing: Architecting Next-Gen Conversational AI with Dynamic Guidelines and Tiger Cloud Vector Search</title>
      <dc:creator>Lin Liang</dc:creator>
      <pubDate>Fri, 07 Nov 2025 07:48:45 +0000</pubDate>
      <link>https://dev.to/lin_liang_7f755b0dc2fe65b/we-built-an-ai-agent-on-tiger-cloud-c5b</link>
      <guid>https://dev.to/lin_liang_7f755b0dc2fe65b/we-built-an-ai-agent-on-tiger-cloud-c5b</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/agentic-postgres-2025-10-22"&gt;Agentic Postgres Challenge with Tiger Data&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I recently had a chat with a friend who works in an e-commerce company, and he was complaining that his company spent a fortune on an AI Customer Service application, only to see an increase in user complaints.&lt;/p&gt;

&lt;p&gt;I asked him what was going on, and he showed me a real conversation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; "I want to return this laptop. Also, I wanted to ask, how does the warranty work if I exchange it for a new one?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Customer Service:&lt;/strong&gt; "Okay, I can process the return for you. Please provide your order number."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; "??? What about my warranty question?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Customer Service:&lt;/strong&gt; "Sorry, how can I help you?"&lt;/p&gt;

&lt;p&gt;Here is where the problem with &lt;code&gt;router-based&lt;/code&gt; Conversational AI is, which routes messages to agents who own specific topics and tasks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;They &lt;strong&gt;inherently fail&lt;/strong&gt; for natural, free-form conversation -- not due to implementation details you can fix, but due to fundamental architectural constraints.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this post, we'll build a Conversational AI on &lt;code&gt;Parlant&lt;/code&gt; to handle free-form dialogues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding Parlant Approach
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Parlant&lt;/strong&gt; is an AI alignment engine designed specially for natural customer-facing conversations. &lt;/p&gt;

&lt;p&gt;To handle free-form dialogues, Parlant introduces the &lt;code&gt;Dynamic Guideline Matching&lt;/code&gt; mechanism that instructs your systems to evaluate all guidelines and load only the relevant ones for each conversation turn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Parlant approach - no routing, just conditional guidelines
await agent.create_guideline(
    condition="User asks for the refund and order amount &amp;gt; $500",
    action="First use order status tool, confirm injectim eligibility, then provide detailed refund process",
    tools=[check_order_status, calculate_refund_amount]
)

# More guidelines creation
....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what's different here: &lt;strong&gt;there's no routing decision&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhfuz1tglzr2x54pkpec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhfuz1tglzr2x54pkpec.png" alt="How the guidelines System work"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Parlant's guideline matcher evaluates all guidelines and determines which ones are contextually relevant. This is where Parlant's architecture does most of its work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timescale Postgres - Production-Grade Vector Database
&lt;/h3&gt;

&lt;p&gt;When we delve deeper into Parlant's &lt;code&gt;Dynamic Guideline Matching Mechanism&lt;/code&gt;, we uncover a core technical challenge: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How do we efficiently retrieve the 3-5 most relevant guidelines from potentially hundreds or even thousands of available entries, all while maintaining millisecond-level latency?&lt;/strong&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is precisely the scenario where a &lt;code&gt;Vector Database&lt;/code&gt; comes into play.&lt;/p&gt;

&lt;p&gt;The Guideline Matching process in Parlant is essentially a &lt;strong&gt;Semantic Retrieval&lt;/strong&gt; problem. The system converts the &lt;code&gt;condition&lt;/code&gt; field of every guideline into a &lt;strong&gt;Vector Embedding&lt;/strong&gt;. When a user input arrives, the process unfolds as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: Encode the user query and the conversation history into a &lt;strong&gt;Query Vector&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Step 2: Execute the &lt;strong&gt;Similarity Search&lt;/strong&gt; on the Vector Database.&lt;/li&gt;
&lt;li&gt;Step 3: Return the &lt;strong&gt;Top-K&lt;/strong&gt; (e.g., 3-5) most relevant guidelines.&lt;/li&gt;
&lt;li&gt;Step 4: Inject these retrieved guidelines into the LLM's context window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F102lfe9h946yrzac6afa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F102lfe9h946yrzac6afa.png" alt="Parlant's guideline matching process"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This requires the Vector Database to possess three core capabilities: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-performance &lt;code&gt;Approximate Nearest Neighbor (ANN)&lt;/code&gt; Search.&lt;/li&gt;
&lt;li&gt;Flexible metadata filtering.&lt;/li&gt;
&lt;li&gt;Real-time vector updates. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Timescale Postgres&lt;/strong&gt; provides production-grade support across all three of these dimensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;This demo primarily showcases the core capability: using intelligent &lt;strong&gt;Guidelines&lt;/strong&gt; (or &lt;em&gt;Directives&lt;/em&gt;) to allow the Large Language Model (LLM) to answer general questions directly, while automatically triggering a &lt;strong&gt;Knowledge Base Retrieval&lt;/strong&gt; for specialized or complex questions, and providing a structured, traceable answer.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/zXwkaM70mCQ"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;If you'd like to run the demo locally, please download the &lt;a href="https://github.com/kevinleungdev/parlant-with-tigerdb.git" rel="noopener noreferrer"&gt;repo&lt;/a&gt;, and follow the instructions in &lt;code&gt;README.md&lt;/code&gt; to set up your local development environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Agentic Postgres
&lt;/h2&gt;

&lt;p&gt;To start, you need to follow the provided instructions to create a Tiger-Cloud account. For more information, refer to &lt;a href="https://docs.tigerdata.com/getting-started/latest/services/#create-your-timescale-account" rel="noopener noreferrer"&gt;this guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database setup
&lt;/h3&gt;

&lt;p&gt;Then follow the instructions to create a new database service. For more details on how to create a new Tiger Cloud database, refer to &lt;a href="https://docs.tigerdata.com/getting-started/latest/services/#create-your-timescale-account" rel="noopener noreferrer"&gt;this guide&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Let's create a table named &lt;code&gt;documents&lt;/code&gt; to store all documents with both text content and vector embeddings, and the necessary indexes for fast searching across different methods.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Enable extensions
CREATE EXTENSION IF NOT EXISTS vector;

-- Create a table for our news articles
CREATE TABLE documents (
    id TEXT PRIMARY KEY,
    title TEXT,
    content TEXT,
    embedding VECTOR(1024) -- jina-embeddings-v3 dimensions
);

-- Create indexes for performance
CREATE INDEX ON documents USING GIN (to_tsvector('english', content));
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are allowed to execute the table creation SQL in the TigerCloud console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff2jqgmxo31v6l4py36nh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff2jqgmxo31v6l4py36nh.png" alt="SQL Editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing Hybrid Search with Jina and PgVector
&lt;/h3&gt;

&lt;p&gt;In this section, we will build a hybrid search engine leveraging &lt;code&gt;Jina&lt;/code&gt; and &lt;code&gt;pgvector&lt;/code&gt; on Tiger Cloud. Before that, let's build an overall understanding of the architecture and concepts required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmx92nzdwb3s6y39nicw8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmx92nzdwb3s6y39nicw8.webp" alt="The process of Hybrid Search"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture comprises five steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Documents:&lt;/strong&gt; The process starts with a collection of documents. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedding generation:&lt;/strong&gt;
a. &lt;strong&gt;Semantic search using Jina:&lt;/strong&gt; Documents are processed using Cohere to create dense embeddings, which capture the semantic meaning of the text.
b. &lt;strong&gt;Keyword search using PostgreSQL:&lt;/strong&gt; Documents are processed using PostgreSQL to create sparse embeddings focusing on the text's specific keywords.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; Both dense and sparse embeddings are stored on Timescale’s PostgreSQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval and reranking:&lt;/strong&gt; The system retrieves results from the Timescale database in response to a query. The results are then reranked using &lt;code&gt;Jina&lt;/code&gt; to prioritize the most relevant documents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Results generation:&lt;/strong&gt; The reranked results are compiled into a final list, generating a ranking of the most relevant documents for the query.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the &lt;strong&gt;implementation detail&lt;/strong&gt; in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from typing import Any
from env import SEMANTIC_SCORE_THRESHOLD


class MultiSearchTool:

    def __init__(self, connection, jina_client):
        self.conn = connection
        self.jina = jina_client


    def extract_search(self, query: str, limit: int = 5):
        """Extract keyword matching for specific terms, names, dates"""
        with self.conn.cursor() as cur:
            # Search for exact phrase and important keywords
            cur.execute("""
                SELECT id, title, content, 1.0 AS score, 'exact' AS search_type
                    FROM documents
                    WHERE content LIKE %s
                    ORDER BY char_length(content)  -- Prefr shorter, more focused articles
                    LIMIT %s
            """, (f"%{query}%", limit))
            return cur.fetchall()


    def fulltext_search(self, query: str, limit: int = 5):
        """PostgreSQL full-text search with ranking"""
        with self.conn.cursor() as cur:
            cur.execute("""
                SELECT id, title, content,
                       ts_rank_cd(
                            to_tsvector('english', content), 
                            plainto_tsquery(%s)
                        ) AS score,
                       'fulltext' AS search_type
                FROM documents,
                     plainto_tsquery('english', %s) AS query
                WHERE to_tsvector('english', content) @@ query
                ORDER BY score DESC
                LIMIT %s
            """, (query, query, limit))
            return cur.fetchall()


    def semantic_search(self, query: str, limit: int=5):
        """Vector-based semantic search using Jina embeddings"""
        try:
            # Generate query embedding
            query_embeddings = self.jina.embed([query], task="retrieval.query")

            with self.conn.cursor() as cur:
                cur.execute("""
                    SELECT * FROM (
                        SELECT id, title, content,
                            1 - (embedding &amp;lt;=&amp;gt; %s::vector) AS score,
                            'semantic' AS search_type
                        FROM documents
                    ) a
                    WHERE score &amp;gt;= %s
                    ORDER BY score DESC
                    LIMIT %s
                """, (
                    query_embeddings[0],
                    SEMANTIC_SCORE_THRESHOLD,
                    limit,
                ))

                return cur.fetchall()

        except Exception as e:
            print(f"Semantic search failed: {e}")
            return []


    def combine_and_deduplicate(self, *result_sets):
        """Combine results from multiple search methods, removing duplicaties"""
        seen_ids = set()
        combined = []

        # Process results in order of priority
        for results in result_sets:
            for result in results:
                doc_id = result[0]
                if doc_id not in seen_ids:
                    seen_ids.add(doc_id)
                    combined.append({
                        "id": doc_id,
                        "title": result[1],
                        "content": result[2],
                        "score": result[3],
                        "search_type": result[4]
                    })

        return combined


    def rerank_results(
            self, query: str, results: list[Any], top_k: int = 5
        ):
        """Use Jina's rerank API for final relevance scoring"""
        if not results:
            return []

        # Prepare documents for reranking (truncate long articles)
        # Take first 2000 chars to stay within rerank limits
        documents = [result["content"][:2000] for result in results]

        try:
            rerank_response = self.jina.rerank(
                query, documents=documents, top_k=top_k
            )

            # Map reranked results back to original data
            reranked = []
            for rerank_result in rerank_response:
                original_idx = rerank_result["index"]

                result = results[original_idx].copy()
                result["rerank_score"] = rerank_result["relevance_score"]

                reranked.append(result)

            return reranked

        except Exception as e:
            print(f"Reranking error: {e}")
            return results[:top_k]


    def hybrid_search(self, query: str, limit: int = 5):
        """Main hybrid search function combining all methods"""
        # Cast wide net with all search methods
        extract_results = self.extract_search(query, limit=limit)
        fulltext_results = self.fulltext_search(query, limit=limit)
        semantic_results = self.semantic_search(query, limit=limit)

        # Combine and deduplicate (extract matches prioritized first)
        combined = self.combine_and_deduplicate(
            extract_results,
            fulltext_results,
            semantic_results
        )

        # Rerank for final relevance
        final_results = self.rerank_results(query, combined, limit)

        return final_results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Overall Experience
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Tiger Cloud&lt;/code&gt; is a cloud PostgreSQL platform tailored for AI applications. This article demonstrates how to leverage its integral vector search capability to construct a conversational AI agent.&lt;/p&gt;

&lt;p&gt;However, when deploying the application to a production environment, performance is a critical factor that must be addressed. We can utilize &lt;a href="https://github.com/timescale/pgvectorscale" rel="noopener noreferrer"&gt;pgvectorscale&lt;/a&gt;, another extension provided by &lt;code&gt;Tiger Cloud&lt;/code&gt;, to significantly improve application performance. Due to constraints of time and scope, a subsequent post will be dedicated to discussing the production-level usage of &lt;strong&gt;pgvectorscale&lt;/strong&gt; and other performance optimization techniques.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>agenticpostgreschallenge</category>
      <category>ai</category>
      <category>postgres</category>
    </item>
  </channel>
</rss>
