ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Repo Assistant API——代码仓库问答助手开发

Repo Assistant API——代码仓库问答助手开发 主要作为个人学习笔记开发顺序先搭 FastAPI PostgreSQL Docker Compose并完成仓库、任务清单 CRUD。加入 Redis 和异步索引任务能下载/读取一个本地或公开 Git 仓库。实现源码过滤、文本切分与索引状态管理。接入 embedding 和pgvector检索。接入 LLM完成“带引用来源”的 RAG 问答。最后补鉴权、失败重试、日志、测试、限流与部署配置。1.1代码流程分析RAG 是Retrieval-Augmented Generation中文常叫“检索增强生成”。意思是模型回答问题前不是只靠自己的记忆直接编答案而是先从你的代码仓库里检索出相关代码片段再把这些片段作为上下文交给大模型回答。可拆分成几个阶段“索引任务会将可读源码按行分块”项目会读取仓库里的源码文件把太长的文件切成一小段一小段的chunk。每个 chunk 会记住它来自哪个文件、从第几行到第几行。这个逻辑如下split_text:#项目会读取仓库里的源码文件把太长的文件切成一小段一小段的 chunk。每个 chunk 会记住它来自哪个文件、从第几行到第几行。 #path:str 表示文件路径 #content:str 表示文件内容 #max_chars:int 表示每个 chunk 的最大字符数 #overlap_lines:int 表示相邻 chunk 的行数重叠。 #最后会返回一个 Chunk 列表 def split_text(path: str, content: str, max_chars: int 1800, overlap_lines: int 8) - list[Chunk]: Keep source line ranges so every generated answer can be cited accurately. lines content.splitlines()#把完整源码文本按行拆开变成一个列表 chunks: list[Chunk] []#创建一个空列表用来保存最终切出来的代码块。类型标注说明它里面放的是 Chunk 对象。 start 0#表示当前代码块从哪一行开始。这里用的是 Python 列表下标所以第一行是 0不是 1。后面保存给用户看的行号时会转成从 1 开始。 while start len(lines): end start size 0 while end len(lines) and (size len(lines[end]) 1 max_chars or end start): size len(lines[end]) 1# 1 还是表示换行符 end 1#结束位置往后移动一行 body \n.join(lines[start:end]).strip()#把 start 到 end 之间的多行重新拼成一段文本 if body: chunks.append(Chunk(pathpath, start_linestart 1, end_lineend, contentbody))#创建一个 Chunk 对象并加入结果列表 if end len(lines): break start max(start 1, end - overlap_lines)#计算下一个代码块从哪里开始,正常情况下下一个块不是直接从 end 开始而是从 end - overlap_lines 开始 return chunks“嵌入后写入 pgvector”每个代码片段会通过 OpenAI embedding 模型转换成一组数字向量。这个向量可以表示这段代码的语义含义。相关函数是embed_texts。然后这些向量会和文件路径、行号、源码内容一起写入数据库的source_chunks表表里的embedding字段使用 pgvector 存储定义在SourceChunk。embed_textsdef _client() - OpenAI: if not settings.openai_api_key: raise RuntimeError(OPENAI_API_KEY is not configured)#抛出异常 return OpenAI(api_keysettings.openai_api_key) def embed_texts(texts: list[str]) - list[list[float]]:#- list[list[float]]类型提示返回一个列表其中每个元素又是浮点数列表 if not texts: return [] client _client()#准备调用服务的客户端右边的 () 表示调用前面定义的 _client 函数再把它返回的对象赋给变量 client。 #真正发送文本让模型生成向量 response client.embeddings.create(#表示通过客户端调用“创建 embedding”的接口 modelsettings.openai_embedding_model,#指定模型 inputtexts,#指定输入的文本列表 dimensionssettings.embedding_dimensions,#指定向量维度 ) return [item.embedding for item in response.data]#返回生成的向量列表 #response.data 是一个包含多个 embedding 对象的列表每个对象都有一个 embedding 属性表示向量。我们使用列表推导式来提取这些向量并返回一个列表。例如输入可以是[用户登录功能, 数据库连接功能]输出结构类似[ [0.12, -0.35, 0.87], # 第一段文本的向量 [0.56, 0.21, -0.14], # 第二段文本的向量 ]“问答按余弦距离召回”用户提问时问题本身也会被转成一个向量。系统会拿“问题向量”和数据库里所有“代码片段向量”比较相似度。余弦距离越小表示语义越接近。代码里这一行就是按相似度排序并取前几个片段ask。“返回文件与行号引用”系统不仅返回答案还会告诉你答案依据来自哪个文件、哪几行代码。比如app/ai.py:14-19这种引用。相关返回结构在Citation生成引用的地方在ask。用一句更白话的话说这个项目会先把代码仓库拆成带行号的小片段转成向量存进 pgvector当你提问时它找出和问题最相关的代码片段再让大模型基于这些片段回答并告诉你答案参考了哪些文件和行号。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进