最近在尝试将大语言模型应用到更复杂的代码生成和长文档分析场景时一个核心瓶颈总是绕不开上下文长度。无论是处理一个庞大的代码仓库还是分析一份冗长的技术文档模型能“记住”和“理解”的文本量即上下文窗口直接决定了任务的上限。传统的几K或几十K token的窗口在动辄几十万行代码的项目面前显得捉襟见肘。“百万token上下文”听起来像是下一代模型的专属能力但通过一些创新的工程架构我们完全有可能在现有或即将到来的模型如传闻中的GPT-5.6或代号Sol的模型上实现近似百万token级别的长上下文处理能力。这其中Codex这里指一种广义的、用于增强代码理解和生成的系统或架构模式而非特指某个已停止服务的产品扮演着至关重要的角色。本文将深入探讨如何利用类Codex的架构思想为高级语言模型构建一个能够处理超长上下文的解决方案。我们将从核心概念拆解开始逐步深入到架构设计、关键实现技术如分块、检索、压缩、状态管理并提供一套可实践的伪代码和配置思路。无论你是希望优化现有AI编程助手还是为未来的大模型应用做准备这篇文章都将提供一套完整的技术蓝图。1. 背景与核心概念为什么需要百万Token上下文在深入技术细节之前我们首先要厘清几个关键概念并理解长上下文需求的迫切性。1.1 Token与上下文窗口Token在自然语言处理中Token是模型处理文本的基本单位。对于英文一个Token可能是一个单词或一个词根对于中文通常是一个字或一个词。模型的内存和计算复杂度与处理的Token数量直接相关。上下文窗口 (Context Window)指模型在一次前向传播中能够接受并处理的Token序列的最大长度。它决定了模型能“看到”多少上文信息来生成下一个Token或做出决策。1.2 为什么代码场景尤其需要长上下文项目级理解一个功能可能分散在多个文件中如Controller, Service, Mapper, Entity。要正确生成或修改代码模型需要同时看到这些相关的文件。依赖关系追踪理解一个函数调用需要知道其定义、参数类型、返回值以及可能抛出的异常这些信息可能分布在不同的模块甚至第三方库的文档中。架构与设计模式重构代码或实现新特性时需要理解整个模块甚至子系统的架构设计这需要大量的上下文信息。调试与排错错误栈、日志文件、配置文件加起来很容易就超过了几万Token。1.3 GPT-5.6 与 Sol对长上下文的期待虽然GPT-5.6和Sol作为假设中的下一代模型代号尚未发布但行业趋势明确指向更长的上下文窗口。然而单纯地线性增加模型的注意力机制会带来计算量的平方级增长O(n²)成本极高。因此必须借助外部系统来“扩展”上下文而非完全依赖模型原生能力。这就是Codex类系统的用武之地。1.4 什么是本文所指的“Codex”本文中的“Codex”不特指OpenAI已停服的Codex模型而是指一套用于处理、索引、检索和向大模型提交代码或文本上下文的辅助系统或架构。它的核心职责是作为大模型如GPT-5.6/Sol的“外部记忆体”和“信息过滤器”将百万Token级别的原始数据提炼成模型能够有效处理的、高质量的相关上下文。2. 架构总览Codex如何扩展上下文实现百万Token上下文绝非简单地将所有文本拼接起来扔给模型。核心思路是“不是所有Token都平等”。我们需要一个智能的架构来管理海量信息。2.1 核心架构图文字描述整个系统可以看作一个处理流水线[原始代码库/文档] → (1) 解析与分块 (Chunking) → (2) 向量化与索引 (Embedding Indexing) → (3) 上下文检索 (Retrieval) → (4) 上下文压缩与组装 (Compression Assembly) → (5) 提交至大模型 (LLM Inference) → (6) 输出与状态更新同时一个对话/会话状态管理器贯穿始终维护当前交互的历史和焦点。2.2 各组件职责解析与分块将源代码、文档按结构如函数、类、文件或语义进行智能切分形成有意义的“块”(Chunk)。向量化与索引使用嵌入模型如text-embedding-ada-002或开源模型将每个“块”转换为向量并存入向量数据库如Pinecone, Weaviate, Milvus, Qdrant。上下文检索根据用户当前的问题或指令将其转换为查询向量从向量数据库中快速找出最相关的若干个“块”。上下文压缩与组装检索到的“块”总和可能仍然很长。此组件负责对它们进行摘要、去重、优先级排序并最终组装成符合模型上下文窗口限制的提示(Prompt)。大模型接口负责与GPT-5.6/Sol等LLM的API交互发送组装好的提示并返回结果。状态管理维护对话历史、当前活跃的文件/模块、用户偏好等确保多轮对话的连贯性。3. 关键技术深度拆解3.1 智能分块策略分块的质量直接决定检索效果。糟糕的分块会割裂语义。基于语法树的分块对于代码使用解析器如Python的astJava的JavaParser将代码按函数、类、方法节点进行切分。这能保持代码结构的完整性。重叠分块在块与块之间设置一定的重叠区域例如50个Token防止关键信息如函数声明被恰好切在边界。元数据附加为每个块附加元数据如文件路径、语言、所属的类/命名空间、在文件中的起止行号等。这些元数据对后续的检索和展示至关重要。示例基于AST的Python代码分块伪代码import ast import tiktoken # 用于计算token class CodeChunker: def __init__(self, token_limit512): self.token_limit token_limit self.encoder tiktoken.get_encoding(cl100k_base) # 假设使用GPT-4的编码 def chunk_file(self, filepath, source_code): 将单个文件按函数/类进行分块 chunks [] try: tree ast.parse(source_code) for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): # 提取节点对应的源代码段 node_source ast.get_source_segment(source_code, node) if node_source: chunk_text f# File: {filepath}\n# Type: {node.__class__.__name__}\n# Name: {node.name}\n\n{node_source} token_count len(self.encoder.encode(chunk_text)) if token_count self.token_limit: chunks.append({ text: chunk_text, metadata: { file: filepath, type: node.__class__.__name__, name: node.name, start_line: node.lineno, token_count: token_count } }) else: # 对于过大的节点进行递归或按语句切分 chunks.extend(self._split_large_node(chunk_text, filepath, node.name)) except SyntaxError as e: # 处理语法错误可能按纯文本分块 chunks.append({ text: f# File: {filepath}\n# [Syntax Error: {e}]\n\n{source_code}, metadata: {file: filepath, type: PlainText, error: str(e)} }) return chunks def _split_large_node(self, text, filepath, name): # 简化的按行或语句切分逻辑 lines text.split(\n) sub_chunks [] current_chunk [] current_token_count 0 for line in lines: line_tokens len(self.encoder.encode(line \n)) if current_token_count line_tokens self.token_limit and current_chunk: sub_chunks.append({ text: \n.join(current_chunk), metadata: {file: filepath, type: PartialFunction, name: name, part: len(sub_chunks)1} }) current_chunk [line] current_token_count line_tokens else: current_chunk.append(line) current_token_count line_tokens if current_chunk: sub_chunks.append({ text: \n.join(current_chunk), metadata: {file: filepath, type: PartialFunction, name: name, part: len(sub_chunks)1} }) return sub_chunks3.2 检索与相关性排序检索的目标是找到与用户查询最相关的代码块。双路检索语义检索使用查询的向量在向量数据库中搜索找到语义相似的块。这是核心。关键词/元数据检索同时可以使用传统的全文搜索如Elasticsearch或基于元数据文件名、函数名的过滤来确保召回那些语义相似度不高但名称直接匹配的关键实体。重排序初步检索可能返回几十个候选块。可以使用一个更小、更快的“重排序模型”或基于规则的策略如最近修改的文件权重更高、被频繁引用的函数权重更高对候选列表进行精排选出Top-K个最相关的块。3.3 上下文压缩与提示工程这是将百万Token“浓缩”进有限窗口的魔法所在。假设模型原生窗口是128K我们需要从检索到的100个块总计可能500K Token中提炼出最核心的128K Token。提取式摘要直接选取相关性最高的前N个块直到总Token数接近窗口上限。简单但有效。抽象式摘要对于相关性稍低的块使用一个较小的、廉价的LLM如Claude Haiku或GPT-3.5-Turbo为其生成一个简短的摘要用摘要代替原始文本。这能大幅节省Token。优先级队列为每个块赋予动态优先级。与当前对话历史高度相关的、用户明确指定的文件中的块优先级提高。提示模板设计精心设计提交给大模型的最终提示。模板应清晰分隔系统指令、对话历史、检索到的上下文标注来源、当前用户问题。示例上下文组装提示模板你是一个专业的代码助手拥有对当前代码库的深度了解。 ## 系统指令 1. 你的回答应基于提供的“相关代码上下文”。 2. 如果上下文不足以回答问题请明确指出缺少什么信息。 3. 生成或修改代码时请严格遵守项目中的代码风格和模式。 ## 对话历史 {history} ## 相关代码上下文 {context} ## 用户问题 {question}3.4 状态管理与会话为了在多轮对话中保持连贯需要维护状态。对话历史保存用户与助手的交互记录。可以采用滑动窗口或摘要的方式管理历史长度防止其无限膨胀。焦点文件用户在当前对话中正在编辑或讨论的文件路径。系统可以自动提升这些文件中代码块的检索优先级。工作区快照定期或在关键操作后对当前检索到的核心代码块集合生成一个“快照”或“摘要”作为下一轮检索的全局背景避免每一轮都从零开始。4. 完整实战案例构建一个简易的Codex代理服务我们将构建一个简化但核心功能完整的服务演示如何为LLM集成长上下文能力。我们将使用Python、FastAPI、ChromaDB向量数据库和OpenAI API模拟未来的GPT-5.6接口。4.1 项目结构与环境准备codex-context-agent/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 应用入口 │ ├── chunker.py # 代码分块模块 │ ├── embedder.py # 向量化模块 │ ├── retriever.py # 检索模块 │ ├── compressor.py # 上下文压缩模块 │ ├── state_manager.py # 状态管理 │ └── prompts.py # 提示模板 ├── data/ # 存放待索引的代码库 ├── chroma_db/ # ChromaDB 持久化存储 ├── requirements.txt └── .envrequirements.txtfastapi0.104.1 uvicorn[standard]0.24.0 openai1.3.0 chromadb0.4.18 langchain0.0.340 # 用于文本分割和工具链 tiktoken0.5.2 python-dotenv1.0.0环境变量 .envOPENAI_API_KEYsk-... # 用于嵌入和LLM调用 MODEL_NAMEgpt-4-1106-preview # 模拟使用长上下文模型 EMBEDDING_MODELtext-embedding-ada-002 CHUNK_SIZE1000 CHUNK_OVERLAP200 TOP_K_RETRIEVAL10 MAX_CONTEXT_TOKENS120000 # 目标上下文长度4.2 核心模块实现分块模块 (chunker.py)import os from langchain.text_splitter import RecursiveCharacterTextSplitter, Language from app.prompts import get_chunk_header class CodeChunker: def __init__(self, chunk_size1000, chunk_overlap200): self.text_splitter RecursiveCharacterTextSplitter.from_language( languageLanguage.PYTHON, chunk_sizechunk_size, chunk_overlapchunk_overlap, separators[\n\n, \n, , ] # 代码特有的分隔符 ) def chunk_directory(self, directory_path): 递归遍历目录对代码文件进行分块 all_chunks [] for root, dirs, files in os.walk(directory_path): for file in files: if self._is_code_file(file): filepath os.path.join(root, file) relative_path os.path.relpath(filepath, directory_path) with open(filepath, r, encodingutf-8, errorsignore) as f: content f.read() # 为文件内容添加头部信息 header get_chunk_header(relative_path, content[:100]) chunks self.text_splitter.split_text(header content) for i, chunk in enumerate(chunks): all_chunks.append({ id: f{relative_path}:{i}, text: chunk, metadata: { source: relative_path, chunk_index: i, file_type: os.path.splitext(file)[1] } }) return all_chunks def _is_code_file(self, filename): code_exts [.py, .java, .js, .ts, .cpp, .c, .h, .go, .rs, .php] return any(filename.endswith(ext) for ext in code_exts)向量化与索引模块 (embedder.py)import chromadb from chromadb.config import Settings from openai import OpenAI import os from dotenv import load_dotenv load_dotenv() class VectorStore: def __init__(self, persist_directory./chroma_db): self.client chromadb.PersistentClient(pathpersist_directory) # 创建一个集合类似数据库的表 self.collection self.client.get_or_create_collection( namecodex_context, metadata{hnsw:space: cosine} # 使用余弦相似度 ) self.openai_client OpenAI(api_keyos.getenv(OPENAI_API_KEY)) self.embedding_model os.getenv(EMBEDDING_MODEL, text-embedding-ada-002) def embed_text(self, text): 调用OpenAI Embedding API生成向量 response self.openai_client.embeddings.create( modelself.embedding_model, inputtext ) return response.data[0].embedding def add_documents(self, chunks): 将分块后的文档添加到向量数据库 ids [chunk[id] for chunk in chunks] texts [chunk[text] for chunk in chunks] metadatas [chunk[metadata] for chunk in chunks] # 批量生成嵌入向量 embeddings [self.embed_text(text) for text in texts] self.collection.add( embeddingsembeddings, documentstexts, metadatasmetadatas, idsids ) print(fAdded {len(chunks)} chunks to vector store.) def query(self, query_text, n_results5, filter_metadataNone): 根据查询文本检索相关文档 query_embedding self.embed_text(query_text) results self.collection.query( query_embeddings[query_embedding], n_resultsn_results, wherefilter_metadata # 可选的元数据过滤如 {source: src/main.py} ) return results检索与压缩模块 (retriever.py compressor.py)# retriever.py from app.embedder import VectorStore import tiktoken class ContextRetriever: def __init__(self, vector_store: VectorStore, top_k10, max_context_tokens120000): self.vector_store vector_store self.top_k top_k self.max_context_tokens max_context_tokens self.encoder tiktoken.get_encoding(cl100k_base) def retrieve(self, query, conversation_history, focus_filesNone): 检索与查询相关的上下文。 focus_files: 列表当前聚焦的文件提升其检索权重。 # 1. 基础语义检索 base_results self.vector_store.query(query, n_resultsself.top_k * 2) # 多取一些用于筛选 # 2. (简化)如果有焦点文件提升其排名 retrieved_docs [] seen_ids set() if base_results and base_results[documents]: for i, doc in enumerate(base_results[documents][0]): doc_id base_results[ids][0][i] metadata base_results[metadatas][0][i] if doc_id not in seen_ids: score 1.0 / (i 1) # 简单评分位置越靠前分数越高 # 如果文档在焦点文件中提升分数 if focus_files and metadata.get(source) in focus_files: score * 2.0 retrieved_docs.append({ id: doc_id, text: doc, metadata: metadata, score: score, token_count: len(self.encoder.encode(doc)) }) seen_ids.add(doc_id) # 3. 按分数排序 retrieved_docs.sort(keylambda x: x[score], reverseTrue) return retrieved_docs[:self.top_k] # compressor.py class ContextCompressor: def __init__(self, max_tokens120000): self.max_tokens max_tokens self.encoder tiktoken.get_encoding(cl100k_base) def compress_and_assemble(self, retrieved_docs, query, history, system_prompt): 压缩检索到的文档并组装成最终的提示。 策略优先选取高分文档直到达到token限制。 # 计算系统提示、历史、查询的token数 system_tokens len(self.encoder.encode(system_prompt)) history_tokens len(self.encoder.encode(history)) query_tokens len(self.encoder.encode(query)) reserved_tokens system_tokens history_tokens query_tokens 500 # 预留buffer available_tokens self.max_tokens - reserved_tokens selected_docs [] total_tokens 0 context_text for doc in retrieved_docs: if total_tokens doc[token_count] available_tokens: selected_docs.append(doc) total_tokens doc[token_count] # 添加上下文块并注明来源 context_text f\n\n--- 来自文件: {doc[metadata][source]} (块 {doc[metadata].get(chunk_index, N/A)}) ---\n{doc[text]} else: # Token不足尝试摘要简化版截断或跳过 # 在实际应用中这里可以调用一个快速的摘要模型 remaining available_tokens - total_tokens if remaining 100: # 如果还有一定空间可以添加摘要 # 模拟摘要取前N个token truncated self.encoder.decode(self.encoder.encode(doc[text])[:remaining-50]) context_text f\n\n--- [摘要] 来自文件: {doc[metadata][source]} ---\n{truncated}...\n break # 不再添加更多文档 final_prompt f{system_prompt} ## 对话历史 {history} ## 相关代码上下文 {context_text if context_text else 本次查询未检索到相关上下文} ## 用户问题 {query} return final_prompt, selected_docs4.3 主应用与API端点 (main.py)from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import List, Optional import uvicorn from app.chunker import CodeChunker from app.embedder import VectorStore from app.retriever import ContextRetriever from app.compressor import ContextCompressor from app.state_manager import ConversationStateManager from openai import OpenAI import os from dotenv import load_dotenv load_dotenv() app FastAPI(titleCodex Context Agent API) # 初始化组件 vector_store VectorStore() chunker CodeChunker( chunk_sizeint(os.getenv(CHUNK_SIZE, 1000)), chunk_overlapint(os.getenv(CHUNK_OVERLAP, 200)) ) retriever ContextRetriever( vector_store, top_kint(os.getenv(TOP_K_RETRIEVAL, 10)), max_context_tokensint(os.getenv(MAX_CONTEXT_TOKENS, 120000)) ) compressor ContextCompressor(max_tokensint(os.getenv(MAX_CONTEXT_TOKENS, 120000))) state_manager ConversationStateManager() openai_client OpenAI(api_keyos.getenv(OPENAI_API_KEY)) LLM_MODEL os.getenv(MODEL_NAME, gpt-4-1106-preview) class IndexRequest(BaseModel): directory_path: str class QueryRequest(BaseModel): session_id: str question: str focus_files: Optional[List[str]] None class QueryResponse(BaseModel): answer: str session_id: str used_context_sources: List[str] total_context_tokens: int app.post(/index) async def index_codebase(request: IndexRequest): 索引一个本地代码目录 if not os.path.isdir(request.directory_path): raise HTTPException(status_code400, detailDirectory not found) chunks chunker.chunk_directory(request.directory_path) vector_store.add_documents(chunks) return {message: fIndexed {len(chunks)} chunks from {request.directory_path}} app.post(/query, response_modelQueryResponse) async def query_with_context(request: QueryRequest): 基于索引的上下文进行问答 # 1. 获取或创建会话状态 state state_manager.get_state(request.session_id) # 2. 检索相关上下文 retrieved_docs retriever.retrieve( queryrequest.question, conversation_historystate.get_conversation_history_summary(), focus_filesrequest.focus_files ) # 3. 压缩并组装提示 system_prompt 你是一个智能代码助手请基于提供的上下文回答问题。如果上下文不足请说明。 final_prompt, used_docs compressor.compress_and_assemble( retrieved_docsretrieved_docs, queryrequest.question, historystate.get_conversation_history(), system_promptsystem_prompt ) # 4. 调用大模型 try: response openai_client.chat.completions.create( modelLLM_MODEL, messages[{role: user, content: final_prompt}], temperature0.2, max_tokens2000 ) answer response.choices[0].message.content except Exception as e: raise HTTPException(status_code500, detailfLLM call failed: {str(e)}) # 5. 更新会话状态 state.add_interaction(request.question, answer) state_manager.save_state(request.session_id, state) # 6. 构造响应 return QueryResponse( answeranswer, session_idrequest.session_id, used_context_sources[doc[metadata][source] for doc in used_docs], total_context_tokenscompressor.encoder.encode(final_prompt) ) if __name__ __main__: uvicorn.run(app, host0.0.0.0, port8000)4.4 运行与验证安装依赖pip install -r requirements.txt配置API Key在.env文件中填入你的OPENAI_API_KEY。准备代码库将你想要索引的代码放入data/目录。启动服务python -m app.main索引代码库使用curl或Postman向http://localhost:8000/index发送POST请求Body为{directory_path: ./data}。进行查询向http://localhost:8000/query发送POST请求Body示例{ session_id: test_session_1, question: 请解释一下项目中的用户认证模块是如何工作的, focus_files: [src/auth/controller.py, src/auth/service.py] }查看结果服务将返回LLM生成的答案并列出回答所参考的源代码文件。5. 常见问题与排查思路在实现和使用此类系统时你可能会遇到以下典型问题问题现象常见原因解决思路检索结果不相关1. 分块策略不合理割裂了语义。2. 嵌入模型不适合代码。3. 查询表述太模糊。1. 尝试基于AST的分块或调整分块大小/重叠。2. 评估并使用针对代码训练的嵌入模型如codebert。3. 在查询中补充更多关键词或上下文。提示超出Token限制1. 检索返回的块太多或太大。2. 对话历史积累过长。1. 降低TOP_K_RETRIEVAL或在压缩阶段启用更积极的摘要。2. 为对话历史设置Token上限或启用历史摘要功能。响应慢1. 嵌入生成或向量检索慢。2. LLM API调用延迟高。1. 使用本地嵌入模型或对向量数据库进行性能调优如使用HNSW索引。2. 考虑对LLM响应进行流式输出或使用缓存。多轮对话中遗忘之前内容状态管理仅保存了文本历史未保存“认知状态”。在状态管理中不仅保存对话文本还保存上一轮使用的核心上下文块的ID或摘要作为下一轮检索的初始过滤器。无法理解跨文件调用检索时未考虑代码的依赖关系。在索引阶段额外构建一个代码调用图。检索时不仅根据语义也根据调用关系找到相关文件如函数A调用了函数B则检索A时也返回B。向量数据库报错Collection not found持久化路径错误或集合未正确创建。检查chroma_db目录权限和路径。在初始化VectorStore时确保使用get_or_create_collection。6. 最佳实践与工程建议要将这个原型系统用于生产环境需要考虑以下工程化细节6.1 分块与索引优化分层索引不仅索引代码块也索引文件级、目录级甚至项目级的摘要。检索时可以先定位到高层级再深入细节。增量更新监控代码仓库变化只对改动的文件进行重新分块和更新向量而不是全量重建索引。混合索引结合向量数据库语义和传统倒排索引关键词、符号提升召回率。6.2 检索质量提升查询扩展自动将用户问题扩展成多个相关查询如“如何实现X”可扩展为“X的实现代码”、“X的示例”、“X的文档”。重排序模型使用一个轻量级的交叉编码器模型对初步检索结果进行精排比单纯的向量相似度更准。反馈学习记录用户对回答的反馈如采纳、修改、拒绝用于调整检索和排序策略。6.3 上下文压缩的进阶策略智能摘要使用一个专门训练的、擅长总结代码的轻量级模型来生成块摘要。Token预算分配为系统提示、历史、检索上下文、用户问题动态分配Token预算确保核心部分有足够空间。基于焦点的动态压缩如果用户连续对话都围绕某个文件可以逐渐压缩其他文件的表示为该文件分配更多Token。6.4 系统性能与成本缓存对常见的查询和其检索结果进行缓存避免重复计算。异步处理索引、嵌入生成等耗时操作应异步进行不阻塞主请求。成本监控密切监控嵌入模型和LLM的API调用成本设置用量告警。对于内部代码可以考虑使用开源嵌入模型。6.5 安全与权限代码泄露风险确保该系统部署在可信的网络环境中并对访问API进行严格的认证和授权。输入过滤对用户输入进行过滤防止注入攻击或恶意消耗资源的查询。输出审查对于生成的代码在应用到生产环境前应有必要的安全扫描和人工审查流程。通过上述架构和实践我们构建了一个能够有效管理和利用百万Token级别代码上下文的系统。它充当了大型语言模型的“外部大脑”通过精密的索引、检索和压缩流程将浩瀚的代码海洋提炼成模型能够消化的精华信息。随着模型本身上下文窗口的不断增长如未来GPT-5.6/Sol可能具备这套系统的价值在于其智能的信息筛选和组装能力而不仅仅是扩展长度。你可以从本文提供的简化原型出发根据实际业务需求在分块策略、检索算法、状态管理等方面进行深度定制和优化最终打造出属于你自己的、强大的AI编程伙伴。