Chuyển đến nội dung chính

Jan-V1: Hướng dẫn kèm Dự án Demo

Tìm hiểu cách xây dựng Trợ lý Nghiên cứu Chuyên sâu bằng khả năng lập luận tác tử của Jan-v1, bao gồm triển khai cục bộ, phát triển ứng dụng Streamlit và hơn thế nữa.
· 12 phút đọc

Khám phá cùng AI

Nhóm Jan gần đây đã phát hành Jan-v1, một mô hình lập luận tác tử tiên tiến mang đến khả năng tự động hóa nghiên cứu tinh vi nhờ tận dụng công cụ nâng cao và giải quyết vấn đề nhiều bước.

Trọng tâm của tôi trong hướng dẫn này là các khả năng lập luận tác tử độc đáo của Jan-v1, minh họa cách nó vận hành như một quy trình nghiên cứu phức tạp thông qua giao diện Streamlit triển khai cục bộ được cung cấp bởi llama.cpp.

Trong hướng dẫn này, tôi sẽ giải thích cách:

  • Triển khai Jan-v1 cục bộ với lượng tử hóa GGUF để tối ưu hiệu năng và quyền riêng tư
  • Xây dựng ứng dụng Streamlit cho nghiên cứu tự động kèm theo theo dõi tiến độ theo thời gian thực
  • Tích hợp tìm kiếm web thông minh với tạo truy vấn động
  • Tạo tổng hợp báo cáo chuyên nghiệp với cấu trúc đầu ra theo định dạng
  • Áp dụng xử lý văn bản nâng cao để đảm bảo tài liệu sạch, sẵn sàng cho doanh nghiệp

Cuối cùng, ứng dụng của bạn sẽ trông như thế này:

Bản demo Trợ lý Nghiên cứu Chuyên sâu

Jan-v1 là gì?

Jan-v1 là một mô hình 4B tham số, xây dựng trên nền tảng Lucy và Qwen3-4B-thinking, được thiết kế cho các quy trình tác tử như sử dụng công cụ, lập luận nhiều bước và nghiên cứu tự động. Khác với các LLM thông thường, Jan-v1 xuất sắc trong điều phối giữa công cụ, nguồn dữ liệu và các bước lập luận thông qua giàn giáo có cấu trúc và phần thưởng điều chỉnh theo hành vi.

Dưới đây là một vài tính năng chính của mô hình này:

  • Kiến trúc sẵn sàng cho tác tử: Mô hình được tối ưu cho tích hợp thực tế, xử lý tìm kiếm web, tổng hợp và tạo báo cáo có cấu trúc bằng lập luận nâng cao và API công cụ.
  • Hiệu quả: Dù kích thước nhỏ, Jan-v1 đạt hiệu năng thường thấy ở các mô hình lớn hơn nhờ huấn luyện RL có trọng tâm, nhấn mạnh đầu ra tập trung và hướng mục tiêu.
  • Triển khai ưu tiên quyền riêng tư: Định dạng GGUF gọn nhẹ cho phép suy luận cục bộ nhanh, lý tưởng cho tác vụ nghiên cứu riêng tư, độ trễ thấp trên laptop hoặc CPU.
  • Huấn luyện tinh chỉnh cho nghiên cứu: Jan-v1 sử dụng các hàm phần thưởng mới cân bằng giữa tính thông tin và độ ngắn gọn, then chốt cho các tác vụ có rủi ro cao như phân tích và báo cáo, nhờ đó giảm thiểu ảo giác và dài dòng.
  • Xây dựng trên Qwen3-4B-thinking: Mô hình thừa hưởng khả năng lập luận chuỗi suy nghĩ và kiểm soát công cụ mạnh mẽ từ chế độ thinking của Qwen3, giúp đặc biệt hiệu quả cho các pipeline tự động hóa nghiên cứu.

Thiết lập Jan-v1 cục bộ

Trong dự án này, chúng ta sẽ triển khai Jan-v1 cục bộ bằng Llama.cpp với phiên bản lượng tử hóa GGUF để tối ưu hiệu năng và tài nguyên.

Bước 1: Cài đặt phụ thuộc

Trước tiên, hãy đảm bảo bạn có các gói Python cần thiết:

pip install llama-cpp-python==0.3.15
pip install streamlit aiohttp 

Lệnh này cài đặt tất cả phụ thuộc cốt lõi cần cho tích hợp mô hình AI, tạo giao diện web và các tác vụ web bất đồng bộ.

Bước 2: Tải phiên bản GGUF của Jan-v1

Trước khi tải mô hình, hãy tạo thư mục mới tên models/ bằng lệnh sau:

 mkdir -p models

Sau đó, tải mô hình bằng lệnh sau:

curl -L https://huggingface.co/janhq/Jan-v1-4B-GGUF/resolve/main/Jan-v1-4B-Q4_K_M.gguf -o Jan-v1-4B-Q4_K_M.gguf

Hoàn tất bước này, bạn đã tải thành công mô hình Jan-v1 4B ở định dạng GGUF và đặt trong thư mục models/. Phiên bản lượng tử hóa này cân bằng tốt giữa hiệu năng và hiệu quả bộ nhớ, lý tưởng cho suy luận cục bộ trên CPU hoặc Colab.

Bước 3: Thiết lập khóa API Serper

Đăng ký hoặc đăng nhập tài khoản Serper của bạn: https://serper.dev/api-keys và chọn thẻ API Keys, sau đó sao chép khóa API mặc định cho bản demo này. Hãy thiết lập khóa này làm biến môi trường như sau:

export SERPER_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxx

Khóa API Serper

Giờ bạn đã sẵn sàng tải và tương tác với mô hình trong pipeline trợ lý nghiên cứu của mình.

Demo: Trợ lý Nghiên cứu Chuyên sâu với Jan-v1

Hãy xây dựng một ứng dụng Streamlit minh họa các khả năng nghiên cứu nâng cao của Jan-v1, với tạo truy vấn thông minh, tìm kiếm web tự động (dùng Serper) và tổng hợp báo cáo chuyên nghiệp.

Bước 1: Import thư viện

Chúng ta bắt đầu bằng cách import tất cả thư viện cần thiết cho trợ lý nghiên cứu. Chúng hỗ trợ từ tạo giao diện web đến tìm kiếm web bất đồng bộ và tích hợp mô hình AI cục bộ.

import streamlit as st
import json
import time
import re
from datetime import datetime
from typing import List, Dict, Optional
import asyncio
import aiohttp
from llama_cpp import Llama
import os
from dataclasses import dataclass

Chúng ta import các thư viện cốt lõi dùng xuyên suốt ứng dụng. Bao gồm thành phần UI (Streamlit), công cụ đồng thời (asyncio, aiohttp) và backend LLM (llama_cpp). Chúng ta cũng bổ sung tiện ích để phân tích cú pháp, gợi ý kiểu và xử lý ngày giờ.

Bước 2: Hàm trợ giúp

Giờ chúng ta sẽ triển khai vài hàm trợ giúp xử lý định dạng báo cáo và trích xuất nội dung. Các hàm này giúp báo cáo tạo ra duy trì chất lượng chuyên nghiệp và cấu trúc nhất quán.

def sections_for_format(fmt: str) -> list[str]:
    fmt = (fmt or "").strip().lower()
    if fmt == "executive":
        return ["EXECUTIVE SUMMARY"]
    if fmt == "detailed":
        return [
            "INTRODUCTION",
            "DETAILED ANALYSIS",
            "CURRENT TRENDS AND DEVELOPMENTS",
            "IMPLICATIONS AND RECOMMENDATIONS",
            "CONCLUSION",
        ]
    if fmt == "academic":
        return [
            "ABSTRACT",
            "INTRODUCTION",
            "METHODOLOGY",
            "FINDINGS",
            "DISCUSSION",
            "CONCLUSION",
        ]
    if fmt == "presentation":
        return [
            "OVERVIEW",
            "KEY INSIGHTS",
            "RECOMMENDATIONS",
            "NEXT STEPS",
            "CONCLUSION",
        ]
    return ["INTRODUCTION", "DETAILED ANALYSIS", "CONCLUSION"]
def extract_final_block(text: str) -> str:
    m = re.search(r"<final>([\s\S]*?)</final>", text, flags=re.IGNORECASE)
    if m:
        cleaned_text = m.group(1).strip()
    else:
        cleaned_text = text
    preamble_patterns = [
        r"^(?:note:|okay,|hmm,|internal|let me|i (?:will|’ll)|as an ai|thinking|plan:|here is your report|the following is|i have prepared|i am presenting|based on the provided information|below is the report|i hope this meets your requirements|this report outlines|this is the final report).*?$",
        r"^(?:Here is the report|I have compiled the report|The report is provided below|This is the requested report).*?$", # More specific preambles
        r"^(?:Please find the report below|Here's the report).*?$"
    ]
    for pattern in preamble_patterns:
        cleaned_text = re.sub(pattern, "", cleaned_text, flags=re.IGNORECASE | re.MULTILINE).strip()
    cleaned_text = re.sub(r"(?m)^\s*[-*•]\s+", "", cleaned_text)
    cleaned_text = re.sub(r"[#`*_]{1,3}", "", cleaned_text)
    headers = [
        "EXECUTIVE SUMMARY","INTRODUCTION","DETAILED ANALYSIS","CURRENT TRENDS AND DEVELOPMENTS",
        "IMPLICATIONS AND RECOMMENDATIONS","CONCLUSION","ABSTRACT","METHODOLOGY","FINDINGS",
        "DISCUSSION","OVERVIEW","KEY INSIGHTS","RECOMMENDATIONS","NEXT STEPS"
    ]
    sorted_headers = sorted(headers, key=len, reverse=True)
    first_pos = -1
    for h in sorted_headers:
        match = re.search(r'\b' + re.escape(h) + r'\b', cleaned_text, flags=re.IGNORECASE)
        if match:
            if first_pos == -1 or match.start() < first_pos:
                first_pos = match.start()
    if first_pos >= 0:
        cleaned_text = cleaned_text[first_pos:].strip()
    return cleaned_text

Cách mỗi hàm đóng vai trò trong pipeline:

  • sections_for_format() function: Hàm này tạo tiêu đề phần phù hợp với định dạng báo cáo đã chọn (ví dụ: executive, chi tiết, học thuật). Điều này đảm bảo mỗi báo cáo tuân thủ chuẩn chuyên nghiệp và đáp ứng kỳ vọng người dùng.
  • extract_final_block() function: Bước hậu xử lý này tách nội dung cốt lõi của báo cáo. Nó trích văn bản giữa thẻ <final> và loại bỏ lời mở đầu, cụm từ đệm và dấu tích Markdown. Nó cũng phát hiện tiêu đề chuẩn sớm nhất (ví dụ “INTRODUCTION”, “SUMMARY”) để loại bỏ phần dẫn không liên quan, đảm bảo đầu ra cuối cùng sạch, có cấu trúc và sẵn sàng sử dụng.

Các hàm trợ giúp này rất quan trọng để duy trì đầu ra nhất quán, chất lượng cao trên các định dạng báo cáo và kịch bản nghiên cứu khác nhau cho một mô hình “biết suy nghĩ” như Jan-v1.

Bước 3: Thiết lập cấu hình

Hãy thiết lập hệ thống cấu hình lõi quản lý tham số mô hình, thiết lập API và tùy chọn nghiên cứu. Cách tiếp cận mô-đun này đảm bảo tùy biến dễ dàng và hiệu năng tối ưu.

Bước 3.1: Cấu hình nghiên cứu

Bước này định nghĩa hệ thống cấu hình và bộ nạp mô hình để suy luận cục bộ hiệu quả với mô hình Jan-v1. Hệ thống được thiết kế để hỗ trợ tác vụ nghiên cứu có thể tái lập trên môi trường chỉ CPU, như laptop hoặc Colab Pro, đồng thời vẫn linh hoạt và an toàn.

@dataclass
class ResearchConfig:
    model_path: str = "models/Jan-v1-4B-Q4_K_M.gguf" #change as per your location
    max_tokens: int = 2048
    temperature: float = 0.6
    top_p: float = 0.95
    top_k: int = 20
    context_length: int = 4096
    search_api_key: str = os.getenv("SERPER_API_KEY", "")
    search_engine: str = "serper"  

Tổng quan cấu hình của đoạn mã trên:

  • Tham số tập trung: ResearchConfig là một @dataclass tập trung tất cả cấu hình LLM và công cụ tìm kiếm, cho phép tinh chỉnh dễ dàng cho các kịch bản nghiên cứu khác nhau.
  • Siêu tham số suy luận: Các tham số như temperature, top_p và top_k được đặt để cân bằng tính sáng tạo với tính xác thực, điều quan trọng cho đầu ra định hướng nghiên cứu.
  • Bộ nhớ và ngữ cảnh: context_length được đặt 4096 token, tối ưu cho khả năng lập luận dài của Jan-v1.
  • Tích hợp API: search_api_key cho phép tích hợp với Serper để tăng cường tìm kiếm web theo thời gian thực.

Bước 3.2: Trợ lý Nghiên cứu Chuyên sâu

Ở bước này, chúng ta định nghĩa lớp Trợ lý Nghiên cứu Chuyên sâu đóng vai trò bộ điều khiển lõi cho mọi tác vụ nghiên cứu, trừu tượng hóa xử lý cấu hình và mô hình cấp thấp trong khi cung cấp giao diện cấp cao cho sinh nội dung, tìm kiếm và tổng hợp.

class DeepResearchAssistant:
    def __init__(self, config: ResearchConfig):
        self.config = config
        self.llm = None
        self.demo_mode = False
    def load_model(self):
        try:
            if not os.path.exists(self.config.model_path):
                print(f"Model file not found: {self.config.model_path}")
                return False
            file_size_gb = os.path.getsize(self.config.model_path) / (1024**3)
            if file_size_gb < 1.0:
                print(f"Model file too small ({file_size_gb:.1f} GB).")
                return False
            self.llm = Llama(
                model_path=self.config.model_path,
                n_ctx=self.config.context_length,
                verbose=False,
                n_threads=max(1, min(4, os.cpu_count() // 2)),
                n_gpu_layers=0,
                use_mmap=True,
                use_mlock=False,
                n_batch=128,
                f16_kv=True,
            )
            test = self.llm("Hi", max_tokens=3, temperature=0.1, echo=False)
            ok = bool(test and 'choices' in test)
            print("Model loaded " if ok else "Model loaded but test generation failed ")
            return ok
        except Exception as e:
            print(f"Model loading failed: {e}")
            return False

Lớp DeepResearchAssistant là bộ điều phối chính quản lý tải mô hình, xác thực cấu hình và cung cấp giao diện sạch cho các thao tác nghiên cứu. Hệ thống cấu hình này có xử lý lỗi và xác thực, đảm bảo mô hình được tải đúng và sẵn sàng cho tác vụ nghiên cứu. Các đặc điểm chính của lớp:

  • Khởi tạo mô hình: Phương thức load_model() khởi tạo an toàn mô hình Jan-v1 GGUF bằng llama-cpp-python, tối ưu cho suy luận CPU cục bộ.
  • Kiểm tra hợp lệ: Trước hết, chúng ta xác minh đường dẫn mô hình và kích thước tệp tối thiểu để phát hiện tệp thiếu hoặc hỏng.
  • Ánh xạ bộ nhớ: Sau đó dùng use_mmap=True để cho phép tải từ đĩa hiệu quả mà không tiêu thụ toàn bộ RAM.
  • Tối ưu suy luận: Đồng thời cấu hình số luồng, kích thước batch và bật f16_kv=True để giảm sử dụng bộ nhớ. Tham số n_gpu_layers=0 đảm bảo chạy thuần CPU.
  • Kiểm tra nhanh: Thực hiện kiểm tra nhanh bằng cách gửi một prompt nhẹ sau khi tải để xác nhận mô hình phản hồi đúng.

Thiết lập này đảm bảo suy luận cục bộ nhanh, gọn nhẹ, lý tưởng để chạy Jan-v1 trên laptop hoặc Colab không có GPU.

Bước 3.3: Sinh phản hồi

Tiếp theo, chúng ta sẽ triển khai hệ thống sinh phản hồi cốt lõi cùng khả năng tìm kiếm web hỗ trợ tự động hóa nghiên cứu. Cho việc này, ta dùng dịch vụ API Serper như dưới đây:

def generate_response(self, prompt: str, max_tokens: int = None, extra_stops: Optional[List[str]] = None) -> str:
        if not self.llm:
            return "Model not loaded."
        stops = ["</s>", "<|im_end|>", "<|endoftext|>"]
        if extra_stops:
            stops.extend(extra_stops)
        mt = max_tokens or self.config.max_tokens
        try:
            resp = self.llm(
                prompt,
                max_tokens=mt,
                temperature=self.config.temperature,
                top_p=self.config.top_p,
                top_k=self.config.top_k,
                stop=stops,
                echo=False
            )
            return resp["choices"][0]["text"].strip()
        except Exception as e:
            return f"Error generating response: {str(e)}"
    async def search_web(self, query: str, num_results: int = 10) -> List[Dict]:
        if self.config.search_api_key:
            return await self.search_serper(query, num_results)
        return []
    async def search_serper(self, query: str, num_results: int) -> List[Dict]:
        url = "https://google.serper.dev/search"
        payload = {"q": query, "num": num_results}
        headers = {"X-API-KEY": self.config.search_api_key, "Content-Type": "application/json"}
        async with aiohttp.ClientSession() as session:
            async with session.post(url, json=payload, headers=headers) as response:
                data = await response.json()
                results = []
                for item in data.get('organic', []):
                    results.append({
                        'title': item.get('title', ''),
                        'url': item.get('link', ''),
                        'snippet': item.get('snippet', ''),
                        'source': 'web'
                    })
                return results
    def generate_search_queries(self, topic: str, focus_area: str, depth: str) -> List[str]:
        counts = {'surface': 5, 'moderate': 8, 'deep': 15, 'comprehensive': 25}
        n = counts.get(depth, 8)
        base = [
            f"{topic} overview",
            f"{topic} recent developments",
            f"{topic} academic studies",
            f"{topic} case studies",
            f"{topic} policy and regulation",
            f"{topic} technical approaches",
            f"{topic} market analysis",
            f"{topic} statistics and data",
        ]
        return base[:n]

Đoạn mã trên triển khai chức năng cốt lõi như sau:

  • generate_response() function: Hàm này xử lý mọi tương tác với mô hình Jan-v1 với xử lý lỗi phù hợp và chuỗi dừng có thể cấu hình. Điều này đảm bảo chất lượng sinh văn bản ổn định cho tổng hợp nghiên cứu.
  • search_web() và search_serper() functions: Các hàm này triển khai tìm kiếm web bất đồng bộ dùng API Serper, cho phép tìm kiếm song song nhanh trên nhiều truy vấn. Thiết kế async tránh khóa giao diện trong quá trình nghiên cứu.
  • generate_search_queries() function: Để tạo truy vấn tìm kiếm đa dạng dựa trên độ sâu nghiên cứu và chủ đề trọng tâm, chúng ta triển khai hàm generate_search_queries(). Nó đảm bảo bao phủ toàn diện chủ đề nghiên cứu, từ tổng quan và cách tiếp cận kỹ thuật đến nghiên cứu tình huống, quy định và phân tích thị trường. Người dùng có thể mở rộng số truy vấn từ 5 đến 25 tùy độ sâu chọn (surface, moderate, deep, hoặc comprehensive).

Hệ thống sinh phản hồi này là lõi của tự động hóa nghiên cứu, kết hợp khả năng lập luận của Jan-v1 với tìm kiếm web thời gian thực để tạo báo cáo nghiên cứu.

Bước 3.4: Tổng hợp nghiên cứu

Tiếp theo, ta triển khai bộ máy tổng hợp nghiên cứu chuyển đổi kết quả tìm kiếm thô thành báo cáo chuyên nghiệp, trau chuốt.

def synthesize_research(self, topic: str, search_results: List[Dict], focus_area: str, report_format: str) -> str:
        context_lines = []
        for i, result in enumerate(search_results[:20]):
            title = result.get("title", "")
            snippet = result.get("snippet", "")
            context_lines.append(f"Source {i+1} Title: {title}\nSource {i+1} Summary: {snippet}")
        context = "\n".join(context_lines)
        sections = sections_for_format(report_format)
        sections_text = "\n".join(sections)
        synthesis_prompt = f"""
You are an expert research analyst. Write the final, polished report on: "{topic}" for a professional, real-world audience.
***CRITICAL INSTRUCTIONS:***
- Your entire response MUST be the final report, wrapped **EXACTLY** inside <final> and </final> tags.
- DO NOT output any text, thoughts, or commentary BEFORE the <final> tag or AFTER the </final> tag.
- DO NOT include any conversational filler, internal thoughts, or commentary about the generation process (e.g., "As an AI...", "I will now summarize...", "Here is your report:").
- DO NOT use markdown formatting (e.g., #, ##, *, -).
- DO NOT use bullet points or lists.
- Maintain a formal, academic/professional tone throughout.
- Ensure the report is complete and self-contained.
- Include the following section headers, in this order, and no others:
{sections_text}
Guidance:
- Base your writing strictly on the Research Notes provided below. If the notes lack specific data, write a careful, methodology-forward analysis without inventing facts or numbers.
Research Notes:
{context}
Now produce ONLY the final report:
<final>
...your report here...
</final>
"""
        raw = self.generate_response(synthesis_prompt, max_tokens=1800, extra_stops=["</final>"])
        final_report = extract_final_block(raw)
        final_report = re.sub(r"(?m)^\s*[-*•]\s+", "", final_report)
        final_report = re.sub(r"[#`*_]{1,3}", "", final_report)
        first = next((h for h in sections if h in final_report), None)
        if first:
            final_report = final_report[final_report.find(first):].strip()
        return final_report

Đoạn mã trên tận dụng khả năng lập luận nâng cao của Jan-v1 để chuyển đổi kết quả tìm kiếm web thô thành các báo cáo chuyên nghiệp vốn thường cần hàng giờ nghiên cứu và viết tay. Hãy cùng hiểu chi tiết các khả năng chính:

  • Nhúng ngữ cảnh: Đoạn mã trên chuyển các kết quả tìm kiếm không cấu trúc thành ghi chú nghiên cứu có cấu trúc bằng cách nối tiêu đề và tóm tắt theo dạng đánh số. Điều này đảm bảo mật độ thông tin cao và giảm phân mảnh ngữ cảnh khi đưa vào mô hình.
  • Xây dựng prompt động: Sử dụng hàm sections_for_format(), chúng ta tạo tiêu đề phần báo cáo phù hợp với định dạng đầu ra đã chọn, được chèn vào prompt kèm quy tắc nghiêm ngặt để ràng buộc hành vi mô hình.
  • Giảm thiểu ảo giác: Bằng cách bắt buộc dùng thẻ <final>...</final> và cấm khung thoại, mô hình bị hạn chế chèn văn bản không liên quan, tóm tắt suy đoán hoặc khung dẫn thừa.
  • Hậu xử lý và chuẩn hóa: Sau khi giải mã, báo cáo được làm sạch bằng bộ lọc regex để loại bỏ dấu tích Markdown, ký hiệu danh sách và định dạng lạc. Hàm cũng đảm bảo đầu ra bắt đầu từ tiêu đề phần hợp lệ đầu tiên để cải thiện tính nhất quán cấu trúc.

Bước 4: Luồng ứng dụng Streamlit chính

Cuối cùng, chúng ta sẽ xây dựng giao diện người dùng hoàn chỉnh kết nối tất cả khả năng nghiên cứu vào một ứng dụng tương tác.

def main():
    st.set_page_config(page_title="Deep Research Assistant", page_icon="", layout="wide")
    st.markdown("""
    <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 1.25rem; border-radius: 10px; color: white; text-align: center; margin-bottom: 1rem;">
        <h1 style="margin:0;"> Deep Research Assistant</h1>
    </div>
    """, unsafe_allow_html=True)
    if 'research_assistant' not in st.session_state:
        config = ResearchConfig()
        st.session_state.research_assistant = DeepResearchAssistant(config)
        st.session_state.model_loaded = st.session_state.research_assistant.load_model()
        st.session_state.research_results = None
    if not st.session_state.research_assistant.config.search_api_key:
        st.warning("SERPER_API_KEY not found in environment. Using fallback demo results.")
    st.header("Research Configuration")
    research_topic = st.text_area(
        "Research Topic",
        placeholder="e.g., Impact of artificial intelligence on healthcare efficiency and patient outcomes",
        height=100
    )
    col1a, col1b = st.columns(2)
    with col1a:
        research_depth = st.selectbox(
            "Research Depth",
            ["surface", "moderate", "deep", "comprehensive"],
            index=1,
            format_func=lambda x: {
                "surface": "Surface (5-8 sources)",
                "moderate": "Moderate (10-15)",
                "deep": "Deep Dive (20-30)",
                "comprehensive": "Comprehensive (40+)"
            }[x]
        )
        focus_area = st.selectbox("Focus Area", ["general", "academic", "business", "technical", "policy"], index=0, format_func=str.title)
    with col1b:
        time_frame = st.selectbox(
            "Time Frame",
            ["current", "recent", "comprehensive"],
            index=1,
            format_func=lambda x: {"current":"Current (≤6 months)","recent":"Recent (≤2 years)","comprehensive":"All time"}[x]
        )
        report_format = st.selectbox(
            "Report Format",
            ["executive", "detailed", "academic", "presentation"],
            index=1,
            format_func=lambda x: {
                "executive":"Executive Summary",
                "detailed":"Detailed Analysis",
                "academic":"Academic Style",
                "presentation":"Presentation Format"
            }[x]
        )
    if st.button("Start Deep Research", type="primary", use_container_width=True):
        if not st.session_state.model_loaded:
            st.error("Model not loaded. See terminal logs for details.")
        elif not research_topic.strip():
            st.error("Please enter a research topic.")
        else:
            start_research(research_topic, research_depth, focus_area, time_frame, report_format)
    if st.session_state.research_results:
        display_research_results(st.session_state.research_results)
def start_research(topic: str, depth: str, focus: str, timeframe: str, format_type: str):
    assistant = st.session_state.research_assistant
    progress_bar = st.progress(0)
    status_text = st.empty()
    try:
        status_text.text("Generating search queries...")
        progress_bar.progress(15)
        queries = assistant.generate_search_queries(topic, focus, depth)
        status_text.text("Searching sources...")
        progress_bar.progress(40)
        all_results = []
        for i, query in enumerate(queries):
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            results = loop.run_until_complete(assistant.search_web(query, 5))
            all_results.extend(results)
            loop.close()
            progress_bar.progress(40 + int(((i + 1) / max(1, len(queries))) * 30))
            time.sleep(0.05)
        status_text.text("Synthesizing report...")
        progress_bar.progress(80)
        research_report = assistant.synthesize_research(topic, all_results, focus, format_type)
        status_text.text("Done.")
        progress_bar.progress(100)
        st.session_state.research_results = {
            'topic': topic,
            'report': research_report,
            'sources': all_results,
            'queries': queries,
            'config': {'depth': depth, 'focus': focus, 'timeframe': timeframe, 'format': format_type},
            'timestamp': datetime.now()
        }
        time.sleep(0.3)
        status_text.empty()
        progress_bar.empty()
    except Exception as e:
        st.error(f"Research failed: {str(e)}")
        status_text.empty()
        progress_bar.empty()
def display_research_results(results: Dict):
    st.header("Research Report")
    st.subheader(f"Topic: {results['topic']}")
    st.markdown(
        f'<div style="background:#f8f9ff;padding:1rem;border-radius:10px;border:1px solid #e1e8ed;">{results["report"]}</div>',
        unsafe_allow_html=True,
    )
    with st.expander("Sources", expanded=False):
        for i, source in enumerate(results['sources'][:12]):
            st.markdown(f"""
            <div style="background:#fff;padding:0.75rem;border-radius:8px;border:1px solid #e1e8ed;margin:0.4rem 0;">
                <h4 style="margin:0 0 .25rem 0;">{source['title']}</h4>
                <p style="margin:0 0 .25rem 0;">{source['snippet']}</p>
                <small><a href="{source['url']}" target="_blank">{source['url']}</a></small>
            </div>
            """, unsafe_allow_html=True)
    st.markdown("### Export")
    c1, c2, c3 = st.columns(3)
    with c1:
        report_text = f"Research Report: {results['topic']}\n\n{results['report']}"
        st.download_button("Download Text", data=report_text, file_name=f"research_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt", mime="text/plain")
    with c2:
        json_data = json.dumps(results, default=str, indent=2)
        st.download_button("Download JSON", data=json_data, file_name=f"research_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json", mime="application/json")
    with c3:
        if st.button("Start New Research"):
            st.session_state.research_results = None
            st.experimental_rerun()
if __name__ == "__main__":
    main()

Đoạn mã trên được chia thành các lớp từ tải mô hình và thu thập đầu vào người dùng đến tạo truy vấn, tìm kiếm web và tổng hợp báo cáo cuối. Hãy cùng bàn chi tiết các lớp này:

  • Lớp UI (giao diện Streamlit): Ứng dụng có tiêu đề đổ màu gradient và bố cục linh hoạt cho trải nghiệm mượt mà. Giao diện trực quan nhưng vẫn cung cấp điều khiển chi tiết tham số nghiên cứu, phù hợp cả người dùng kỹ thuật và không kỹ thuật.
  • Pipeline có thể cấu hình: Người dùng có thể tùy biến quy trình nghiên cứu với bộ chọn độ sâu, lĩnh vực trọng tâm (general, academic, business), khung thời gian và kiểu đầu ra (executive, academic, v.v.).
  • Quản lý tác tử có trạng thái: DeepResearchAssistant được khởi tạo một lần mỗi phiên dùng st.session_state, giữ trạng thái mô hình và tránh tải lại nhiều lần. 
  • Phản hồi thời gian thực và tìm kiếm async: Tiến độ được theo dõi với thanh tiến trình và trạng thái động của Streamlit. Tìm kiếm web thực hiện bất đồng bộ qua asyncio, đảm bảo thực thi truy vấn song song mà không làm kém phản hồi UI.
  • Vòng lặp truy vấn và tổng hợp: Truy vấn tìm kiếm được tự động tạo dựa trên chủ đề và độ sâu bằng chiến lược prompt. Kết quả thu được được tổng hợp và đưa vào mô hình Jan-v1 để tạo báo cáo có cấu trúc.
  • Trình bày và xuất đầu ra: Báo cáo cuối được hiển thị trong khối Markdown với nguồn tham khảo có thể mở rộng. Người dùng có thể xuất báo cáo ở định dạng TXT hoặc JSON hoặc chạy lại quy trình chỉ với một cú nhấp.

Để tự thử, lưu mã thành app.py và chạy:

streamlit run app.py

Kết luận

Trợ lý Nghiên cứu Chuyên sâu này cho thấy sức mạnh thực tiễn của khả năng lập luận nâng cao trong Jan-v1 khi tự động hóa các quy trình nghiên cứu phức tạp. Hệ thống kết hợp thành công:

  • Tự động hóa thông minh: Cho phép nghiên cứu end-to-end từ tạo truy vấn đến tổng hợp báo cáo chuyên nghiệp, loại bỏ hàng giờ làm thủ công.
  • Thiết kế có thể mở rộng: Tính năng độ sâu nghiên cứu có thể cấu hình thích ứng từ tổng quan nhanh đến phân tích toàn diện theo nhu cầu người dùng.
  • Chất lượng chuyên nghiệp: Hỗ trợ tạo các báo cáo phù hợp cho báo cáo điều hành, nghiên cứu học thuật và tài liệu kỹ thuật.
  • Khả năng tiếp cận người dùng: Giao diện này đưa khả năng nghiên cứu AI tiên tiến đến với người dùng dù có hay không nền tảng kỹ thuật.

Ứng dụng minh họa cách tận dụng độ chính xác SimpleQA 91,1% và khả năng lập luận nâng cao của Jan-v1 để xây dựng công cụ sẵn sàng sản xuất, mang lại giá trị tức thì trong quy trình nghiên cứu, phân tích và tạo nội dung.

Aashi Dutt

Tác giả

Aashi Dutt

Tôi là Chuyên gia Google Developers trong lĩnh vực ML (Gen AI), Chuyên gia Kaggle 3x và Đại sứ Women Techmakers với hơn 3 năm kinh nghiệm trong ngành công nghệ. Tôi đồng sáng lập một startup công nghệ y tế vào năm 2020 và hiện đang theo học thạc sĩ khoa học máy tính tại Georgia Tech, chuyên sâu về học máy.

Chủ đề