1. 前言      

     Llmaindex支持与OpenAI、Anthropic、Google、Hugging Face等平台的集成(基本都是国外的平台,要使用国内的大模型API下次要自定义LLM)。

对于国内的Qwen、Deepseek……这些模型,对应的解决办法:

在LlamaIndex抽象中自定义LLMs |LlamaIndex OSS 文档

        我们可以将这些LLM抽象嵌入LlamaIndex的其他模块(索引、检索器、查询引擎、代理),这样就能在数据上构建高级工作流程。

默认情况下,官方使用OpenAI的模型,因此这里我们需要自定义所使用的的LLM。

        为了理解起来不那么抽象,先上代码。

代码实现

1) 使用自定义LLM

要使用自定义LLM模型,需要实现类(或实现一些简单接口)——负责将文本传递给模型并返回新生成的标记。

import logging
from typing import Any
from openai import OpenAI
from pydantic import  Field

from llama_index.core.llms import (
    CustomLLM,
    CompletionResponse,
    CompletionResponseGen,
    LLMMetadata,
)
from llama_index.core.llms.callbacks import llm_completion_callback
from llama_index.core.callbacks import CallbackManager


# --- LlamaIndex 自定义 LLM 实现 ---
class MyLLM(CustomLLM):
    """适配 LlamaIndex 的 LLM 自定义类"""
    api_key: str = "xxx"
    base_url: str = "xxx"
    model_name: str = "xxx"
    timeout: int = 120

    # 使用 Field 默认工厂创建实例
    callback_manager: CallbackManager = Field(default_factory=CallbackManager, exclude=True)

    @property
    def metadata(self) -> LLMMetadata:
        """配置模型元数据,LlamaIndex 会据此切分上下文"""
        return LLMMetadata(
            context_window=32768,
            num_output=4096,
            model_name=self.model_name,
        )

    @llm_completion_callback()
    def complete(self, prompt: str, **kwargs: Any) -> CompletionResponse:
        # 非流式调用
        response = self.Chat(prompt=prompt, steam=True)

        # OpenAI 返回的是对象,需要取 .content
        content = response if isinstance(response, str) else response.choices[0].message.content
        return CompletionResponse(text=content)

    @llm_completion_callback()
    def stream_complete(self, prompt: str, **kwargs: Any) -> CompletionResponseGen:
        # 流式调用
        response = self.Chat(prompt=prompt,stream=True)

        def response_generator():
            full_content = ""
            for chunk in response:
                if chunk.choices and chunk.choices[0].delta.content:
                    delta = chunk.choices[0].delta.content
                    full_content += delta
                    yield CompletionResponse(text=full_content, delta=delta)

        return response_generator()

    # 请求执行工具
    def Chat(self, prompt: str, stream: bool):
        # 请求
        client = OpenAI(api_key=self.api_key, base_url=self.base_url, timeout=self.timeout)

        try:
            return client.chat.completions.create(
                model=self.model_name,
                messages=[{"role": "system", "content": "你是一个聪明的 AI 助手"},
                          {"role": "user", "content": prompt}],
                max_tokens=4096,
                temperature=0.7,
                stream=stream
            )
        except Exception as e:
            logging.basicConfig(level=logging.INFO)
            logger = logging.getLogger(__name__)
            logger.error(f"LLM API调用失败: {e}")
            raise

2) 案例1

from my_llm import MyLLM
import os

def main():
    # 检查数据目录
    data_dir = "./data"
    if not os.path.exists(data_dir):
        print(f"错误: 目录 {data_dir} 不存在。请将 PDF 或文本文件放入该目录。")
        return

    # A. 初始化组件
    print("正在初始化嵌入模型和 LLM...")
    Settings.llm = MyLLM()
    # 确保本地路径或模型名称正确
    Settings.embed_model = HuggingFaceEmbedding(model_name="./models/bge-base-zh-v1.5")

    # B. 构建索引
    print("正在加载文档并构建索引...")
    # 1.加载指定文件
    documents = SimpleDirectoryReader(input_files=["./data/A.txt"]).load_data()
    # 2.加载指定目录下的所有文件
    # documents = SimpleDirectoryReader(data_dir).load_data()
    index = VectorStoreIndex.from_documents(documents)

    # C. 创建查询引擎 (开启流式输出)
    query_engine = index.as_query_engine(streaming=True)

    # D. 执行查询
    question = "为什么可乐比雪碧好喝?"
    print(f"\n查询问题: {question}")
    print("查询结果: ", end="")

    response = query_engine.query(question)

    # E. 处理响应
    if hasattr(response, "response_gen"):
        for text in response.response_gen:
            print(text, end="", flush=True)
    else:
        print(response.response)

    print("\n\n查询完成。")

if __name__ == "__main__":
    main()

//执行结果:

3) 案例2

from llama_index.core import Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
# Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-base-en-v1.5")
Settings.embed_model = HuggingFaceEmbedding(model_name="./models/bge-base-zh-v1.5")

from llama_index.core import SimpleDirectoryReader

# 加载文件
A_docs = SimpleDirectoryReader(input_files=["./data/A.txt"]).load_data()
B_docs = SimpleDirectoryReader(input_files=["./data/B.txt"]).load_data()

# 从文档中创建索引
from llama_index.core import VectorStoreIndex
A_index = VectorStoreIndex.from_documents(A_docs)
B_index = VectorStoreIndex.from_documents(B_docs)

# 持久化索引
from llama_index.core import StorageContext
A_index.storage_context.persist(persist_dir="./storage/A")
B_index.storage_context.persist(persist_dir="./storage/B")

# 从本地读取索引
from llama_index.core import load_index_from_storage
try:
    storage_context = StorageContext().from_defaults(
        persist_dir="./storage/A",
    )
    A_index =load_index_from_storage(storage_context=storage_context)
    storage_context = StorageContext.from_defaults(
        persist_dir="./storage/B",
    )
    B_index =load_index_from_storage(storage_context=storage_context)

    index_loaded = True
except:
    index_loaded = False

# 配置大模型
from my_llm import MyLLM
llm = MyLLM()

# 创建查询引擎
A_engine = A_index.as_query_engine(streaming=True)
B_engine = B_index.as_query_engine(streaming=True)

# 配置查询工具
from llama_index.core.tools import QueryEngineTool
from llama_index.core.tools import ToolMetadata
query_engine_tools = [
    QueryEngineTool(
        query_engine=A_engine,
        metadata=ToolMetadata(
            name="A_Report",
            description=(
                "这是一个用于检索“可乐比雪碧好喝”相关观点、原因和核心分析的工具。当用户询问可乐的优点、可乐与雪碧的对比、或者为什么可乐更好喝时,请使用此工具。输入应该是用户的具体问题,例如“可乐有什么优点?”或“为什么可乐比雪碧好喝?”。"
            )
        )
    ),
    QueryEngineTool(
        query_engine=B_engine,
        metadata=ToolMetadata(
            name="B_Report",
            description=(
                "这是一个用于检索“雪碧比可乐好喝”相关观点、原因和核心分析的工具。当用户询问雪碧的优点、雪碧与可乐的对比、或者为什么雪碧更好喝时,请使用此工具。输入应该是用户的具体问题,例如“雪碧有什么优点?”或“为什么雪碧比可乐好喝?”。"
            )
        )
    )
]

# 创建ReAct Agent
from llama_index.core.agent import ReActAgent
agent = ReActAgent(tools=query_engine_tools, llm=llm, verbose=True)

# 让 Agent 完成任务
print(agent.run("可乐和雪碧哪个更好喝,为什么?详细描述它们各自的优点是什么?"))

//执行结果:

代码分析

1 MyLLM

        在RAG(检索增强生成)框架中,LlamaIndex就像是一个精密的底座,而 LLM 是它的动力源。当你不想使用官方支持的 OpenAI 或 Anthropic,而是使用本地模型(如通过 vLLM、Ollama 或自己写的 API 服务)时,你就需要通过这种方式告诉 LlamaIndex:“嘿,请按我的规则去调用这个模型。”

        这是一个非常典型的适配器模式(Adapter Pattern)代码,它的核心作用是让 LlamaIndex 这个框架能够识别并调用 OpenAI 格式的任意大模型(例如千问、DeepSeek、Claude等)。这段代码将 OpenAI 的 SDK 接口“翻译”成了 LlamaIndex 框架要求的接口标准。

1) 类定义

  • CustomLLM: 这是 LlamaIndex 提供的基类。继承它意味着你的类必须实现特定的接口(如 completemetadata),这样 LlamaIndex 的其他组件(如 QueryEngine)才能像调用官方模型一样调用你的模型。
  • classback_manager字段理解:
    • default_factory=CallbackManager:每次我创建一个新的 LLM 实例时,请自动帮我新建一个独立的 CallbackManager 对象,不要让我所有实例共用一个。
    • exclude=True:以后如果我要把这个 LLM 配置导出成 JSON 文件或者发送给 API 时,请把这个字段扔掉,不要包含在数据里,因为它没法被序列化。”
class MyLLM(CustomLLM):
    """适配 LlamaIndex 的 LLM 自定义类"""
    api_key: str = "xxx"
    base_url: str = "xxx"
    model_name: str = "xxx"
    timeout: int = 120

    # ----- 回调管理:让Agent能够回头看前面的推理思考记录 ------
    # 使用 Field 默认工厂创建实例
    callback_manager: CallbackManager = Field(default_factory=CallbackManager, exclude=True)

2) 元数据配置

作用:这部分定义了模型的基本属性和能力,帮助llamaIndex理解这个模型的“参数”。

@property
def metadata(self) -> LLMMetadata:
    return LLMMetadata(
        context_window=32768,  # 告诉框架:我这个模型最长能吃多少 Token
        num_output=4096,       # 告诉框架:我一次最多吐多少 Token
        model_name=self.model_name,
    )

2) 同步接口 (complete)

作用:用于简单的、一次性的问答请求。

注意点@llm_completion_callback() 装饰器非常重要,它负责触发 LlamaIndex 的内部监控和追踪(比如计算 Token 消耗或显示进度条)。

@llm_completion_callback()
def complete(self, prompt: str, **kwargs: Any) -> CompletionResponse:
    response = self.Chat(prompt=prompt, stream=False) # 注意此处逻辑应为非流式
    content = ... # 解析逻辑
    return CompletionResponse(text=content)

3) 流式接口 (stream_complete)

核心逻辑:这里使用了一个生成器(Generator)。LlamaIndex 要求流式输出必须返回 CompletionResponse(text=累计内容, delta=新增内容),这样前端才能实现像打字机一样的丝滑效果。

@llm_completion_callback()
def stream_complete(self, prompt: str, **kwargs: Any) -> CompletionResponseGen:
    response = self.Chat(prompt=prompt, stream=True)
    def response_generator():
        # ... 迭代拼接字符串 ...
        yield CompletionResponse(text=full_content, delta=delta)
    return response_generator()

4) 底层请求实现(Chat)

这是真正编写网络请求的地方。代码里使用了标准的 openai SDK 来调用一个兼容 OpenAI 格式的后端接口。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐