带重排的RAG工作流
本笔记本将指导您如何设置一个 Workflow 来执行带重排序功能的基础RAG。
!pip install -U llama-indeximport os
os.environ["OPENAI_API_KEY"] = "sk-proj-..."[可选] 使用 Llamatrace 设置可观测性
Section titled “[Optional] Set up observability with Llamatrace”设置追踪功能以可视化工作流中的每个步骤。
%pip install "openinference-instrumentation-llama-index>=3.0.0" "opentelemetry-proto>=1.12.0" opentelemetry-exporter-otlp opentelemetry-sdkfrom opentelemetry.sdk import trace as trace_sdkfrom opentelemetry.sdk.trace.export import SimpleSpanProcessorfrom opentelemetry.exporter.otlp.proto.http.trace_exporter import ( OTLPSpanExporter as HTTPSpanExporter,)from openinference.instrumentation.llama_index import LlamaIndexInstrumentor
# Add Phoenix API Key for tracingPHOENIX_API_KEY = "<YOUR-PHOENIX-API-KEY>"os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"api_key={PHOENIX_API_KEY}"
# Add Phoenixspan_phoenix_processor = SimpleSpanProcessor( HTTPSpanExporter(endpoint="https://app.phoenix.arize.com/v1/traces"))
# Add them to the tracertracer_provider = trace_sdk.TracerProvider()tracer_provider.add_span_processor(span_processor=span_phoenix_processor)
# Instrument the applicationLlamaIndexInstrumentor().instrument(tracer_provider=tracer_provider)!mkdir -p data!wget --user-agent "Mozilla" "https://arxiv.org/pdf/2307.09288.pdf" -O "data/llama2.pdf"由于工作流是异步优先的,这一切在笔记本中都能正常运行。如果您在自己的代码中运行,如果还没有运行异步事件循环,您需要使用 asyncio.run() 来启动一个异步事件循环。
async def main(): <async code>
if __name__ == "__main__": import asyncio asyncio.run(main())RAG + 重排序包含一些明确定义的步骤
- 索引数据,创建索引
- 使用该索引 + 查询来检索相关文本片段
- 使用原始查询对检索到的文本块进行重新排序
- 综合生成最终响应
考虑到这一点,我们可以创建事件和工作流步骤来遵循这个流程!
为了处理这些步骤,我们需要定义几个事件:
- 将检索到的节点传递给重排序器的事件
- 将重新排序的节点传递给合成器的事件
其他步骤将使用内置的 StartEvent 和 StopEvent 事件。
from llama_index.core.workflow import Eventfrom llama_index.core.schema import NodeWithScore
class RetrieverEvent(Event): """Result of running retrieval"""
nodes: list[NodeWithScore]
class RerankEvent(Event): """Result of running reranking on retrieved nodes"""
nodes: list[NodeWithScore]定义好事件后,我们就可以构建工作流和步骤了。
请注意,工作流会自动使用类型注解进行自我验证,因此我们步骤中的类型注解非常有用!
from llama_index.core import SimpleDirectoryReader, VectorStoreIndexfrom llama_index.core.response_synthesizers import CompactAndRefinefrom llama_index.core.postprocessor.llm_rerank import LLMRerankfrom llama_index.core.workflow import ( Context, Workflow, StartEvent, StopEvent, step,)
from llama_index.llms.openai import OpenAIfrom llama_index.embeddings.openai import OpenAIEmbedding
class RAGWorkflow(Workflow): @step async def ingest(self, ctx: Context, ev: StartEvent) -> StopEvent | None: """Entry point to ingest a document, triggered by a StartEvent with `dirname`.""" dirname = ev.get("dirname") if not dirname: return None
documents = SimpleDirectoryReader(dirname).load_data() index = VectorStoreIndex.from_documents( documents=documents, embed_model=OpenAIEmbedding(model_name="text-embedding-3-small"), ) return StopEvent(result=index)
@step async def retrieve( self, ctx: Context, ev: StartEvent ) -> RetrieverEvent | None: "Entry point for RAG, triggered by a StartEvent with `query`." query = ev.get("query") index = ev.get("index")
if not query: return None
print(f"Query the database with: {query}")
# store the query in the global context await ctx.store.set("query", query)
# get the index from the global context if index is None: print("Index is empty, load some documents before querying!") return None
retriever = index.as_retriever(similarity_top_k=2) nodes = await retriever.aretrieve(query) print(f"Retrieved {len(nodes)} nodes.") return RetrieverEvent(nodes=nodes)
@step async def rerank(self, ctx: Context, ev: RetrieverEvent) -> RerankEvent: # Rerank the nodes ranker = LLMRerank( choice_batch_size=5, top_n=3, llm=OpenAI(model="gpt-4o-mini") ) print(await ctx.store.get("query", default=None), flush=True) new_nodes = ranker.postprocess_nodes( ev.nodes, query_str=await ctx.store.get("query", default=None) ) print(f"Reranked nodes to {len(new_nodes)}") return RerankEvent(nodes=new_nodes)
@step async def synthesize(self, ctx: Context, ev: RerankEvent) -> StopEvent: """Return a streaming response using reranked nodes.""" llm = OpenAI(model="gpt-4o-mini") summarizer = CompactAndRefine(llm=llm, streaming=True, verbose=True) query = await ctx.store.get("query", default=None)
response = await summarizer.asynthesize(query, nodes=ev.nodes) return StopEvent(result=response)就这样!让我们稍微探索一下我们编写的工作流程。
- 我们有两个入口点(接受
StartEvent的步骤) - 步骤自身决定何时可以运行
- 工作流上下文用于存储用户查询
- 节点被传递,最终返回一个流式响应
w = RAGWorkflow()
# Ingest the documentsindex = await w.run(dirname="data")# Run a queryresult = await w.run(query="How was Llama2 trained?", index=index)async for chunk in result.async_response_gen(): print(chunk, end="", flush=True)Query the database with: How was Llama2 trained?Retrieved 2 nodes.How was Llama2 trained?Reranked nodes to 2Llama 2 was trained through a multi-step process that began with pretraining using publicly available online sources. This was followed by the creation of an initial version of Llama 2-Chat through supervised fine-tuning. The model was then iteratively refined using Reinforcement Learning with Human Feedback (RLHF) methodologies, which included rejection sampling and Proximal Policy Optimization (PPO).
During pretraining, the model utilized an optimized auto-regressive transformer architecture, incorporating robust data cleaning, updated data mixes, and training on a significantly larger dataset of 2 trillion tokens. The training process also involved increased context length and the use of grouped-query attention (GQA) to enhance inference scalability.
The training employed the AdamW optimizer with specific hyperparameters, including a cosine learning rate schedule and gradient clipping. The models were pretrained on Meta’s Research SuperCluster and internal production clusters, utilizing NVIDIA A100 GPUs.