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技术支持
defsections_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"]defextract_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 preamblesr"^(?: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 =-1for h in sorted_headers:match= re.search(r'\b'+ re.escape(h)+r'\b', cleaned_text, flags=re.IGNORECASE)ifmatch:if first_pos ==-1ormatch.start()< first_pos:
first_pos =match.start()if first_pos >=0:
cleaned_text = cleaned_text[first_pos:].strip()return cleaned_text技术支持
本步骤定义了一个配置系统和模型加载器,用于在仅 CPU 环境(如笔记本或 Colab Pro)中高效地对 Jan-v1 进行本地推理,同时保持灵活与安全。
@dataclassclassResearchConfig:
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"技术支持
defsynthesize_research(self, topic:str, search_results: List[Dict], focus_area:str, report_format:str)->str:
context_lines =[]for i, result inenumerate(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技术支持
Jan 团队近期发布了 Jan-v1,这是一款先进的代理式推理模型,通过强化的工具使用与多步问题求解,引入了成熟的研究自动化能力。
本教程将聚焦 Jan-v1 独特的代理式推理能力,展示它如何通过基于 llama.cpp 的本地部署和 Streamlit 界面,充当复杂的研究工作流。
在本教程中,您将学到:
完成后,您的应用将如下所示:
什么是 Jan-v1?
Jan-v1 是一款拥有 40 亿参数的模型,基于 Lucy 和 Qwen3-4B-thinking 基座构建,专为工具调用、多步推理与自动化研究等代理式工作流而设计。与传统 LLM 不同,Jan-v1 擅长通过结构化脚手架与行为对齐奖励,在工具、数据源与推理步骤之间高效协同。
以下是该模型的一些关键特性:
在本地设置 Jan-v1
在本项目中,我们将使用
Llama.cpp搭配 GGUF 量化版本在本地部署 Jan-v1,以获得最佳性能与资源效率。步骤 1:安装依赖
首先,确保您已安装所需的 Python 包:
该命令将安装 AI 模型集成、网页界面构建与异步网络操作所需的核心依赖。
步骤 2:下载 Jan-v1 的 GGUF 版本
下载模型前,使用以下命令创建名为
models/的新目录:然后使用以下命令下载模型:
完成此步骤后,您已成功下载 GGUF 格式的 Jan-v1 4B 模型,并将其放入
models/目录。该量化版本在性能与内存效率之间取得良好平衡,非常适合在 CPU 或 Colab 上进行本地推理。步骤 3:配置 Serper API 密钥
注册或登录您的 Serper 账户: https://serper.dev/api-keys,选择 API Keys 选项卡,然后复制本演示所用的默认 API 密钥。将该密钥设置为环境变量如下:
现在,您已准备好在研究助手流水线中加载并与模型交互。
演示:基于 Jan-v1 的深度研究助手
我们来构建一个 Streamlit 应用,演示 Jan-v1 的高级研究能力,包括智能查询生成、自动网页搜索(使用 Serper)以及专业报告综合。
步骤 1:导入库
首先导入研究助手所需的全部库,涵盖从网页界面构建到异步网页搜索与本地 AI 模型集成等功能。
我们导入了应用使用的核心库,包括 UI 组件(Streamlit)、并发工具(asyncio、aiohttp)以及 LLM 后端(llama_cpp)。同时引入了解析、类型标注与日期处理等实用工具。
步骤 2:辅助函数
接下来实现若干用于报告格式化与内容提取的辅助函数,以确保生成的报告具备专业质量与一致结构。
各函数在流程中的作用如下:
sections_for_format()函数: 根据所选报告格式(如 executive、detailed、academic)生成相应的章节标题,确保每份报告符合专业标准并满足用户预期。extract_final_block()函数: 该后处理步骤会提取核心报告内容:抓取<final>标签内的文本并移除开场白、冗余短语与 Markdown 痕迹;同时检测最早出现的标准标题(如 "INTRODUCTION"、"SUMMARY"),去除不相关的引导性文本,确保最终输出干净、结构清晰、可直接使用。这些辅助函数对于在不同报告格式与研究场景下,保持像 Jan-v1 这类思维型模型输出的一致性与高质量至关重要。
步骤 3:配置设置
我们建立一个核心配置系统,用于管理模型参数、API 设置与研究偏好。该模块化方法便于自定义与性能优化。
步骤 3.1:研究配置
本步骤定义了一个配置系统和模型加载器,用于在仅 CPU 环境(如笔记本或 Colab Pro)中高效地对 Jan-v1 进行本地推理,同时保持灵活与安全。
上述代码的配置概览如下:
ResearchConfig是一个@dataclass,集中管理所有 LLM 与搜索引擎配置,便于在不同研究场景下进行调优。temperature、top_p与top_k等参数在创造性与事实性之间取得平衡,这对研究取向的输出尤为重要。context_length设为 4096 token,优化以支持 Jan-v1 的长文本推理能力。search_api_key支持接入 Serper,用于实时网页搜索增强。步骤 3.2:深度研究助手
本步骤定义一个深度研究助手类,作为所有研究任务的核心控制器。它抽象了底层模型与配置管理,同时提供用于生成、搜索与综合的高级接口。
DeepResearchAssistant类是主要的编排器,负责管理模型加载、配置校验,并提供简洁的研究操作接口。该配置系统具备错误处理与校验,确保模型正确加载并可投入研究任务。其关键特性包括:load_model()方法通过llama-cpp-python安全初始化 Jan-v1 GGUF 模型,并针对本地 CPU 推理进行了优化。use_mmap=True启用高效的基于磁盘加载,无需占用全部内存。f16_kv=True以降低内存使用;n_gpu_layers=0确保仅使用 CPU 执行。该设置确保了快速、轻量的本地推理,非常适合在无 GPU 的笔记本或 Colab 上运行 Jan-v1。
步骤 3.3:生成响应
接下来实现核心的响应生成系统与网页搜索能力,为研究自动化提供支撑。这里我们使用 Serper API,如下所示:
上述代码的核心功能拆解如下:
generate_response()函数:负责与 Jan-v1 的交互,具备完善的错误处理与可配置的停止序列,确保研究综合文本生成的一致性与高质量。search_web()与search_serper()函数:使用 Serper API 实现异步网页搜索,支持对多条查询的快速并行检索;异步设计避免在研究过程中阻塞 UI。generate_search_queries()函数:根据研究深度与关注主题生成多样化搜索查询,实现从综述、技术路径到案例、监管与市场分析的全覆盖。可按所选深度(surface、moderate、deep、comprehensive)将查询数在 5 至 25 条之间伸缩。该响应生成系统是研究自动化的核心,将 Jan-v1 的推理能力与实时网页搜索结合,用于生成研究报告。
步骤 3.4:综合研究结果
接下来实现研究综合引擎,将原始搜索结果转化为打磨完备、专业规范的报告。
上述代码利用 Jan-v1 的高级推理能力,将原始网页搜索结果转化为通常需要数小时手工研究与写作才能完成的专业报告。其关键能力包括:
sections_for_format()依据所选输出格式生成章节标题,并以严格规则注入提示,约束模型行为。<final>...</final>标签并禁止对话式脚手架,避免模型引入无关文本、臆测性总结或重复性框架。regex过滤器去除 Markdown 痕迹、列表标记与零散格式,并确保输出从首个有效章节标题开始,以增强结构一致性。步骤 4:主 Streamlit 应用流程
最后我们构建完整的用户界面,将上述研究能力集成为一个交互式应用。
上述代码自下而上划分为多个层次:从模型加载与用户输入采集,到查询生成、网页搜索,再到最终报告综合。各层次要点如下:
DeepResearchAssistant通过st.session_state在会话中只初始化一次,保留模型状态,避免重复加载。asyncio异步执行,在并行查询的同时保持界面响应。TXT或JSON,或一键重新运行流程。若要亲自尝试,将代码保存为
app.py并启动: