用STORM研究本地文档

APPLICATION Nov 3, 2024

LLM 代理的使用越来越普遍,用于解决多步骤长上下文研究任务,而传统的 RAG 直接提示方法有时会遇到困难。

在本文中,我们将探讨斯坦福大学开发的一种新的有前途的技术,称为通过检索和多视角提问来综合主题大纲 (STORM),它使用 LLM 代理模拟“视角引导对话”以达到复杂的研究目标并生成丰富的研究文章,可供人类在写作前研究中使用。

STORM 最初是为了从网络来源收集信息而开发的,但也支持搜索本地文档向量存储。在本文中,我们将了解如何使用美国联邦紧急事务管理局的灾难准备和援助文档在本地 PDF 上实施 STORM 以进行 AI 支持的研究。

看到使用 LLM 进行知识检索在相对较短的时间内取得进展真是令人惊讶。自 2020 年发表第一篇关于检索增强生成 (RAG) 的论文以来,我们看到生态系统不断发展,涵盖了丰富的可用技术。其中一种更先进的技术是代理 RAG,其中 LLM 代理迭代和改进文档检索以解决更复杂的研究任务。它类似于人类进行研究的方式,探索一系列不同的搜索查询以更好地了解背景,有时与其他人讨论主题,并将所有内容综合成最终结果。单轮 RAG 即使采用查询扩展和重新排名等技术,也难以应对像这样更复杂的多跳研究任务。

使用代理框架(例如 AutogenCrewAILangGraph)以及特定的 AI 研究助手(例如 GPT Researcher)进行知识检索的模式相当多。在本文中,我们将介绍斯坦福大学基于 LLM 的研究写作系统,即通过检索和多视角提问来综合主题大纲 (STORM)。

1、STORM AI 研究写作系统

STORM 采用了一项巧妙的技术,其中 LLM 代理模拟“视角引导对话”以达到研究目标,并扩展“大纲驱动的 RAG”以生成更丰富的文章。

它配置为生成维基百科风格的文章,并由 10 名经验丰富的维基百科编辑人员进行了测试。

对 10 名经验丰富的维基百科编辑人员对 STORM 感知有用性的调查结果。来源

总体而言,反响不错,70% 的编辑人员认为在研究主题的写作前阶段,这将是一个有用的工具。我希望未来的调查可以涵盖超过 10 位编辑者,但需要注意的是,作者还使用 FreshWiki(最近高质量维基百科文章的数据集)对传统文章生成方法进行了基准测试,发现 STORM 的表现优于以前的技术。

10 位经验丰富的维基百科编辑对 STORM 和 oRAG 生成的 20 对文章进行了人工评估。每对文章由两位维基百科编辑进行评估。来源

STORM 是开源的,可作为 Python 包使用,并使用 LangGraph 等框架进行其他实现。最近,STORM 得到了增强,以支持称为 Co-STORM 的人机协作知识管理,将人类置于 AI 辅助研究循环的中心。

虽然它在自动和人工评估方面都明显优于基线方法,但作者承认有一些注意事项。它还不是多模式的,无法生成经验丰富的人类质量内容——我觉得它还没有为此做好准备,更适合写作前的研究,而不是最终的文章——而且参考文献方面有一些细微差别,需要将来做一些工作。也就是说,如果你有一个深入的研究任务,它值得一试。

你可以在线试用 STORM——它很有趣!——配置为使用网络上的信息进行研究。

但是用你自己的数据运行 STORM 怎么样?

许多组织都希望将 AI 研究工具与自己的内部数据一起使用。STORM 的作者很好地记录了使用 STORM 与不同的 LLM 提供商和本地向量数据库的各种方法,这意味着可以在你自己的文档上运行 STORM。

让我们试试吧!

设置和代码

你可以在这里找到这篇文章的代码,其中包括环境设置说明以及如何整理此演示的一些示例文档。

2、FEMA 灾难准备和援助文件

我们将使用美国联邦紧急事务管理局 (FEMA) 创建的 34 份 PDF 文档来帮助人们准备和应对灾难。这些文档可能不是人们通常想要用来撰写深入研究文章的文档,但我有兴趣了解 AI 如何帮助人们为灾难做好准备。

……我已经编写了处理一些早期博客文章中 FEMA 报告的代码,我已将其包含在上面链接的 repo 中。😊

3、解析和分块

一旦我们有了文档,就需要将它们拆分成更小的文档,以便 STORM 可以在语料库中搜索特定主题。

鉴于 STORM 最初旨在生成维基百科风格的文章,我选择尝试两种方法,

  • 使用 LangChain 的 PyPDFLoader 按页面将文档拆分为子文档,以创建包含多个子主题的维基百科页面的粗略模拟。许多 FEMA PDF 都是单页文档,看起来与维基百科文章没有太大不同;
  • 将文档进一步分块成更小的部分,更有可能涵盖一个离散的子主题。

这些当然是非常基本的解析方法,但我想看看结果如何根据这两种技术而变化。任何认真使用 STORM 处理本地文档的行为都应该投资于所有围绕配对优化的​​常见乐趣。

def parse_pdfs():
    """
    Parses all PDF files in the specified directory and loads their content.

    This function iterates through all files in the directory specified by PDF_DIR,
    checks if they have a .pdf extension, and loads their content using PyPDFLoader.
    The loaded content from each PDF is appended to a list which is then returned.

    Returns:
        list: A list containing the content of all loaded PDF documents.
    """
    docs = []
    pdfs = os.listdir(PDF_DIR)
    print(f"We have {len(pdfs)} pdfs")
    for pdf_file in pdfs:
        if not pdf_file.endswith(".pdf"):
            continue
        print(f"Loading PDF: {pdf_file}")
        file_path = f"{PDF_DIR}/{pdf_file}"
        loader = PyPDFLoader(file_path)
        docs = docs + loader.load()
        print(f"Loaded {len(docs)} documents")

    return docs


docs = parse_pdfs()

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = text_splitter.split_documents(docs)

4、元数据丰富

STORM 的示例文档要求文档具有元数据字段“URL”、“title”和“description”,其中“URL”应该是唯一的。由于我们正在拆分 PDF 文档,因此我们没有单个页面和块的标题和描述,因此我选择使用简单的 LLM 调用来生成这些内容。

对于 URL,我们有单个 PDF 页面的 URL,但也有页面内的块的 URL。复杂的知识检索系统可以拥有由布局检测模型生成的元数据,因此可以在相应的 PDF 中突出显示文本块区域,但对于此演示,我只是在 URL 中添加了一个“_id”查询参数,它只确保它们对于块是唯一的。

def summarize_text(text, prompt):
    """
    Generate a summary of some text based on the user's prompt

    Args:

    text (str) - the text to analyze
    prompt (str) - prompt instruction on how to summarize the text, eg 'generate a title'

    Returns:

    summary (text) - LLM-generated summary

    """
    messages = [
        (
            "system",
            "You are an assistant that gives very brief single sentence description of text.",
        ),
        ("human", f"{prompt} :: \n\n {text}"),
    ]
    ai_msg = llm.invoke(messages)
    summary = ai_msg.content
    return summary


def enrich_metadata(docs):
    """
    Uses an LLM to populate 'title' and 'description' for text chunks

    Args:

    docs (list) - list of LangChain documents

    Returns:

    docs (list) - list of LangChain documents with metadata fields populated

    """
    new_docs = []
    for doc in docs:

        # pdf name is last part of doc.metadata['source']
        pdf_name = doc.metadata["source"].split("/")[-1]

        # Find row in df where pdf_name is in URL
        row = df[df["Document"].str.contains(pdf_name)]
        page = doc.metadata["page"] + 1
        url = f"{row['Document'].values[0]}?id={str(uuid4())}#page={page}"

        # We'll use an LLM to generate a summary and title of the text, used by STORM
        # This is just for the demo, proper application would have better metadata
        summary = summarize_text(doc.page_content, prompt="Please describe this text:")
        title = summarize_text(
            doc.page_content, prompt="Please generate a 5 word title for this text:"
        )

        doc.metadata["description"] = summary
        doc.metadata["title"] = title
        doc.metadata["url"] = url
        doc.metadata["content"] = doc.page_content

        # print(json.dumps(doc.metadata, indent=2))
        new_docs.append(doc)

    print(f"There are {len(docs)} docs")

    return new_docs


docs = enrich_metadata(docs)
chunks = enrich_metadata(chunks)

5、构建矢量数据库

STORM 已经支持 Qdrant 矢量存储。我喜欢尽可能使用 LangChain 和 Llama Index 等框架,以便以后更轻松地更改提供商,因此我选择使用 LangChain 构建本地 Qdrant 矢量数据库,并将其持久保存到本地文件系统,而不是 STORM 的自动矢量数据库管理。我觉得这提供了更多的控制权,而且对于那些已经拥有用于填充文档向量存储的管道的人来说更容易识别。

def build_vector_store(doc_type, docs):
    """
    Givena  list of LangChain docs, will embed and create a file-system Qdrant vector database.
    The folder includes doc_type in its name to avoid overwriting.

    Args:

    doc_type (str) - String to indicate level of document split, eg 'pages',
                     'chunks'. Used to name the database save folder
    docs (list) - List of langchain documents to embed and store in vector database

    Returns:

    Nothing returned by function, but db saved to f"{DB_DIR}_{doc_type}".

    """

    print(f"There are {len(docs)} docs")

    save_dir = f"{DB_DIR}_{doc_type}"

    print(f"Saving vectors to directory {save_dir}")

    client = QdrantClient(path=save_dir)

    client.create_collection(
        collection_name=DB_COLLECTION_NAME,
        vectors_config=VectorParams(size=num_vectors, distance=Distance.COSINE),
    )

    vector_store = QdrantVectorStore(
        client=client,
        collection_name=DB_COLLECTION_NAME,
        embedding=embeddings,
    )

    uuids = [str(uuid4()) for _ in range(len(docs))]

    vector_store.add_documents(documents=docs, ids=uuids)


build_vector_store("pages", docs)
build_vector_store("chunks", docs)

6、运行 STORM

STORM repo 有一些关于不同搜索引擎和 LLM 的优秀示例,以及使用 Qdrant 向量存储的示例。我决定将这些功能中的各种功能结合起来,再加上一些额外的后处理,如下所示:

  • 增加了与 OpenAI 或 Ollama 一起运行的能力
  • 增加了对传入矢量数据库目录的支持
  • 添加了一个函数来解析参考文献元数据文件,以将参考文献添加到生成的精美文章中。 STORM 在 JSON 文件中生成了这些参考文献,但没有自动将它们添加到输出文章中。我不确定这是否是因为我错过了一些设置,但参考文献是评估任何 AI 研究技术的关键,所以我添加了这个自定义后处理步骤。
  • 最后,我注意到开放模型在模板和角色方面有更多的指导,因为它们遵循的指令不如商业模型准确。我喜欢这些控件的透明度,并将它们留给 OpenAI,以便我可以在未来的工作中进行调整。

所有内容都在这里(请参阅 repo 笔记本以获取完整代码)...

def set_instructions(runner):
    """
    Adjusts templates and personas for the STORM AI Research algorithm.

    Args:

    runner - STORM runner object

    Returns:

    runner - STORM runner object with extra prompting

    """

    # Open LMs are generally weaker in following output format.
    # One way for mitigation is to add one-shot example to the prompt to exemplify the desired output format.
    # For example, we can add the following examples to the two prompts used in StormPersonaGenerator.
    # Note that the example should be an object of dspy.Example with fields matching the InputField
    # and OutputField in the prompt (i.e., dspy.Signature).
    find_related_topic_example = Example(
        topic="Knowledge Curation",
        related_topics="https://en.wikipedia.org/wiki/Knowledge_management\n"
        "https://en.wikipedia.org/wiki/Information_science\n"
        "https://en.wikipedia.org/wiki/Library_science\n",
    )
    gen_persona_example = Example(
        topic="Knowledge Curation",
        examples="Title: Knowledge management\n"
        "Table of Contents: History\nResearch\n  Dimensions\n  Strategies\n  Motivations\nKM technologies"
        "\nKnowledge barriers\nKnowledge retention\nKnowledge audit\nKnowledge protection\n"
        "  Knowledge protection methods\n    Formal methods\n    Informal methods\n"
        "  Balancing knowledge protection and knowledge sharing\n  Knowledge protection risks",
        personas="1. Historian of Knowledge Systems: This editor will focus on the history and evolution of knowledge curation. They will provide context on how knowledge curation has changed over time and its impact on modern practices.\n"
        "2. Information Science Professional: With insights from 'Information science', this editor will explore the foundational theories, definitions, and philosophy that underpin knowledge curation\n"
        "3. Digital Librarian: This editor will delve into the specifics of how digital libraries operate, including software, metadata, digital preservation.\n"
        "4. Technical expert: This editor will focus on the technical aspects of knowledge curation, such as common features of content management systems.\n"
        "5. Museum Curator: The museum curator will contribute expertise on the curation of physical items and the transition of these practices into the digital realm.",
    )
    runner.storm_knowledge_curation_module.persona_generator.create_writer_with_persona.find_related_topic.demos = [
        find_related_topic_example
    ]
    runner.storm_knowledge_curation_module.persona_generator.create_writer_with_persona.gen_persona.demos = [
        gen_persona_example
    ]

    # A trade-off of adding one-shot example is that it will increase the input length of the prompt. Also, some
    # examples may be very long (e.g., an example for writing a section based on the given information), which may
    # confuse the model. For these cases, you can create a pseudo-example that is short and easy to understand to steer
    # the model's output format.
    # For example, we can add the following pseudo-examples to the prompt used in WritePageOutlineFromConv and
    # ConvToSection.
    write_page_outline_example = Example(
        topic="Example Topic",
        conv="Wikipedia Writer: ...\nExpert: ...\nWikipedia Writer: ...\nExpert: ...",
        old_outline="# Section 1\n## Subsection 1\n## Subsection 2\n"
        "# Section 2\n## Subsection 1\n## Subsection 2\n"
        "# Section 3",
        outline="# New Section 1\n## New Subsection 1\n## New Subsection 2\n"
        "# New Section 2\n"
        "# New Section 3\n## New Subsection 1\n## New Subsection 2\n## New Subsection 3",
    )
    runner.storm_outline_generation_module.write_outline.write_page_outline.demos = [
        write_page_outline_example
    ]
    write_section_example = Example(
        info="[1]\nInformation in document 1\n[2]\nInformation in document 2\n[3]\nInformation in document 3",
        topic="Example Topic",
        section="Example Section",
        output="# Example Topic\n## Subsection 1\n"
        "This is an example sentence [1]. This is another example sentence [2][3].\n"
        "## Subsection 2\nThis is one more example sentence [1].",
    )
    runner.storm_article_generation.section_gen.write_section.demos = [
        write_section_example
    ]

    return runner

def latest_dir(parent_folder):
    """
    Find the most recent folder (by modified date) in the specified parent folder.

    Args:
        parent_folder (str): The path to the parent folder where the search for the most recent folder will be conducted. Defaults to f"{DATA_DIR}/storm_output".

    Returns:
        str: The path to the most recently modified folder within the parent folder.
    """
    # Find most recent folder (by modified date) in DATA_DIR/storm_data
    # TODO, find out how exactly storm passes back its output directory to avoid this hack
    folders = [f.path for f in os.scandir(parent_folder) if f.is_dir()]
    folder = max(folders, key=os.path.getmtime)

    return folder


def generate_footnotes(folder):
    """
    Generates footnotes from a JSON file containing URL information.

    Args:
        folder (str): The directory path where the 'url_to_info.json' file is located.

    Returns:
        str: A formatted string containing footnotes with URLs and their corresponding titles.
    """

    file = f"{folder}/url_to_info.json"

    with open(file) as f:
        data = json.load(f)

    refs = {}
    for rec in data["url_to_unified_index"]:
        val = data["url_to_unified_index"][rec]
        title = data["url_to_info"][rec]["title"].replace('"', "")
        refs[val] = f"- {val} [{title}]({rec})"

    keys = list(refs.keys())
    keys.sort()

    footer = ""
    for key in keys:
        footer += f"{refs[key]}\n"

    return footer, refs


def generate_markdown_article(output_dir):
    """
    Generates a markdown article by reading a text file, appending footnotes, 
    and saving the result as a markdown file.

    The function performs the following steps:
    1. Retrieves the latest directory using the `latest_dir` function.
    2. Generates footnotes for the article using the `generate_footnotes` function.
    3. Reads the content of a text file named 'storm_gen_article_polished.txt' 
       located in the latest directory.
    4. Appends the generated footnotes to the end of the article content.
    5. Writes the modified content to a new markdown file named 
       STORM_OUTPUT_MARKDOWN_ARTICLE in the same directory.

    Args:

    output_dir (str) - The directory where the STORM output is stored.


    """

    folder = latest_dir(output_dir)
    footnotes, refs = generate_footnotes(folder)

    with open(f"{folder}/storm_gen_article_polished.txt") as f:
        text = f.read()

    # Update text references like [10] to link to URLs
    for ref in refs:
        print(f"Ref: {ref}, Ref_num: {refs[ref]}")
        url = refs[ref].split("(")[1].split(")")[0]
        text = text.replace(f"[{ref}]", f"\[[{ref}]({url})\]")

    text += f"\n\n## References\n\n{footnotes}"

    with open(f"{folder}/{STORM_OUTPUT_MARKDOWN_ARTICLE}", "w") as f:
        f.write(text)

def run_storm(topic, model_type, db_dir):
    """
    This function runs the STORM AI Research algorithm using data
    in a QDrant local database.

    Args:

    topic (str) - The research topic to generate the article for
    model_type (str) - One of 'openai' and 'ollama' to control LLM used
    db_dir (str) - Directory where the QDrant vector database is
    
    """
    if model_type not in ["openai", "ollama"]:
        print("Unsupported model_type")
        sys.exit()

    # Clear lock so can be read
    if os.path.exists(f"{db_dir}/.lock"):
        print(f"Removing lock file {db_dir}/.lock")
        os.remove(f"{db_dir}/.lock")

    print(f"Loading Qdrant vector store from {db_dir}")

    engine_lm_configs = STORMWikiLMConfigs()

    if model_type == "openai":

        print("Using OpenAI models")

        # Initialize the language model configurations
        openai_kwargs = {
            "api_key": os.getenv("OPENAI_API_KEY"),
            "temperature": 1.0,
            "top_p": 0.9,
        }

        ModelClass = (
            OpenAIModel
            if os.getenv("OPENAI_API_TYPE") == "openai"
            else AzureOpenAIModel
        )
        # If you are using Azure service, make sure the model name matches your own deployed model name.
        # The default name here is only used for demonstration and may not match your case.
        gpt_35_model_name = (
            "gpt-4o-mini"
            if os.getenv("OPENAI_API_TYPE") == "openai"
            else "gpt-35-turbo"
        )
        gpt_4_model_name = "gpt-4o"
        if os.getenv("OPENAI_API_TYPE") == "azure":
            openai_kwargs["api_base"] = os.getenv("AZURE_API_BASE")
            openai_kwargs["api_version"] = os.getenv("AZURE_API_VERSION")

        # STORM is a LM system so different components can be powered by different models.
        # For a good balance between cost and quality, you can choose a cheaper/faster model for conv_simulator_lm
        # which is used to split queries, synthesize answers in the conversation. We recommend using stronger models
        # for outline_gen_lm which is responsible for organizing the collected information, and article_gen_lm
        # which is responsible for generating sections with citations.
        conv_simulator_lm = ModelClass(
            model=gpt_35_model_name, max_tokens=10000, **openai_kwargs
        )
        question_asker_lm = ModelClass(
            model=gpt_35_model_name, max_tokens=10000, **openai_kwargs
        )
        outline_gen_lm = ModelClass(
            model=gpt_4_model_name, max_tokens=10000, **openai_kwargs
        )
        article_gen_lm = ModelClass(
            model=gpt_4_model_name, max_tokens=10000, **openai_kwargs
        )
        article_polish_lm = ModelClass(
            model=gpt_4_model_name, max_tokens=10000, **openai_kwargs
        )

    elif model_type == "ollama":

        print("Using Ollama models")

        ollama_kwargs = {
            # "model": "llama3.2:3b",
            "model": "llama3.1:latest",
            # "model": "qwen2.5:14b",
            "port": "11434",
            "url": "http://localhost",
            "stop": (
                "\n\n---",
            ),  # dspy uses "\n\n---" to separate examples. Open models sometimes generate this.
        }

        conv_simulator_lm = OllamaClient(max_tokens=500, **ollama_kwargs)
        question_asker_lm = OllamaClient(max_tokens=500, **ollama_kwargs)
        outline_gen_lm = OllamaClient(max_tokens=400, **ollama_kwargs)
        article_gen_lm = OllamaClient(max_tokens=700, **ollama_kwargs)
        article_polish_lm = OllamaClient(max_tokens=4000, **ollama_kwargs)

    engine_lm_configs.set_conv_simulator_lm(conv_simulator_lm)
    engine_lm_configs.set_question_asker_lm(question_asker_lm)
    engine_lm_configs.set_outline_gen_lm(outline_gen_lm)
    engine_lm_configs.set_article_gen_lm(article_gen_lm)
    engine_lm_configs.set_article_polish_lm(article_polish_lm)

    max_conv_turn = 4
    max_perspective = 3
    search_top_k = 10
    max_thread_num = 1
    device = "cpu"
    vector_db_mode = "offline"

    do_research = True
    do_generate_outline = True
    do_generate_article = True
    do_polish_article = True

    # Initialize the engine arguments
    output_dir=f"{STORM_OUTPUT_DIR}/{db_dir.split('db_')[1]}"
    print(f"Output directory: {output_dir}")

    engine_args = STORMWikiRunnerArguments(
        output_dir=output_dir,
        max_conv_turn=max_conv_turn,
        max_perspective=max_perspective,
        search_top_k=search_top_k,
        max_thread_num=max_thread_num,
    )

    # Setup VectorRM to retrieve information from your own data
    rm = VectorRM(
        collection_name=DB_COLLECTION_NAME,
        embedding_model=EMBEDDING_MODEL,
        device=device,
        k=search_top_k,
    )

    # initialize the vector store, either online (store the db on Qdrant server) or offline (store the db locally):
    if vector_db_mode == "offline":
        rm.init_offline_vector_db(vector_store_path=db_dir)

    # Initialize the STORM Wiki Runner
    runner = STORMWikiRunner(engine_args, engine_lm_configs, rm)

    # Set instructions for the STORM AI Research algorithm
    runner = set_instructions(runner)

    # run the pipeline
    runner.run(
        topic=topic,
        do_research=do_research,
        do_generate_outline=do_generate_outline,
        do_generate_article=do_generate_article,
        do_polish_article=do_polish_article,
    )
    runner.post_run()
    runner.summary()

    generate_markdown_article(output_dir)

我们已准备好运行 STORM!

对于研究主题,我选择了一些难以用典型的 RAG 系统回答的问题,而 PDF 数据中没有很好地涵盖这些问题,因此我们可以看到归因的效果如何……

“比较不同类型的灾难对财务的影响以及这些灾难对社区的影响”

对两个数据库运行此操作……

query = "Compare the financial impact of different types of disasters and how those impact communities"

for doc_type in ["pages", "chunks"]:
    db_dir = f"{DB_DIR}_{doc_type}"
    run_storm(query=query, model_type="openai", db_dir=db_dir)

使用 OpenAI,该过程在我的 Macbook pro M2(16GB 内存)上大约需要 6 分钟。我要指出的是,其他更简单的查询(我们在底层文档中有更多支持内容)要快得多(在某些情况下不到 30 秒)。

7、STORM 结果

STORM 生成一组输出文件……

STORM 生成的文件,从中创建一个 markdown 文件,将精炼的文章与参考脚注相结合

查看 dialogue_log.json 和 llm_call_history.json 以查看视角引导的对话组件很有趣。

对于我们的研究主题……

“比较不同类型灾难的财务影响以及这些影响如何影响社区”

你可以在此处找到生成的文章……

一些快速观察

此演示未进行正式评估 — 这可能比单跳 RAG 系统更复杂 — 但这里有一些主观观察,可能有用也可能没用……

  • 按页面或按较小块解析会产生合理的预读报告,人类可以使用这些报告来研究与灾难的财务影响相关的领域
  • 两种配对方法都提供了引文,但使用较小的块似乎会导致引文较少。例如,请参阅上述两篇文章中的摘要部分。为分析提供依据的参考文献越多越好!
  • 按较小的块解析似乎有时会产生不相关的引文,这是 STORM 论文中提到的引文挑战之一。例如,请参见摘要部分中来源“10”的引用,它与参考句子不符。
  • 总体而言,正如预期的那样,对于在 Wiki 文章上开发的算法,按 PDF 拆分文本似乎可以产生更具凝聚力和扎实的文章(对我来说!)

尽管输入的研究主题在基础文档中没有得到深入介绍,但生成的报告是进一步进行人工分析的一个很好的起点

8、未来的工作

我们在本文中没有讨论 Co-Storm,它将人类带入了循环。这似乎是 AI 赋能研究的一个很好的方向,也是我正在研究的东西。

未来的工作还可以考虑根据业务案例调整系统提示和角色。目前,这些提示针对的是类似维基百科的过程……

STORM 系统提示,说明了对创建维基百科风格文章的重视。来源

另一个可能的方向是将 STORM 的连接器扩展到 Qdrant 之外,例如,包括其他向量存储,或者更好的是,对 Langchain 和 llama 索引向量存储的通用支持。作者鼓励这种事情,未来我可能会发布一个涉及此文件的 PR。

在没有互联网连接的情况下运行 STORM 将是一件了不起的事情,因为它为现场的 AI 协助开辟了可能性。从演示代码中可以看到,我添加了使用 Ollama 本地托管模型运行 STORM 的功能,但令牌吞吐率对于 LLM 代理讨论阶段来说太低了,因此系统无法在我的笔记本电脑上使用小型量化模型完成。也许是未来博客文章的主题!

最后,虽然在线用户界面非常好,但 repo 附带的演示 UI 非常基础,不能用于生产。也许斯坦福团队可能会发布高级界面——也许它已经在某个地方了?——如果没有,那么这里就需要工作了。

9、结束语

这是一个快速演示,希望能帮助人们开始在自己的文档上使用 STORM。我还没有进行系统评估,如果在实时环境中使用 STORM,显然需要这样做。话虽如此,我对它似乎能够获得一个相对细致入微的研究课题并生成被充分引用的写前研究内容印象深刻,这将有助于我自己的研究。


原文链接:Running the STORM AI Research System with Your Local Documents

汇智网翻译整理,转载请标明出处

Tags