精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

RAG多崗位簡(jiǎn)歷篩選系統(tǒng)實(shí)踐:多租戶(hù)架構(gòu)設(shè)計(jì)模式與源碼解讀

人工智能
系統(tǒng)的實(shí)際效果演示、四層系統(tǒng)架構(gòu)拆解、五點(diǎn)核心技術(shù)實(shí)現(xiàn)、三個(gè)二次開(kāi)發(fā)場(chǎng)景指南,以及對(duì)端側(cè)模型應(yīng)用的一些感想。

我在8月底的時(shí)候,發(fā)過(guò)一篇基于 LlamaIndex+LangChain 框架,開(kāi)發(fā)的簡(jiǎn)歷篩選助手的應(yīng)用。后續(xù)有星球成員提出希望能增加多個(gè)崗位的管理功能,正好接下來(lái)的校招活動(dòng)可以用的上。

這篇在原項(xiàng)目的基礎(chǔ)上,核心實(shí)現(xiàn)了多崗位并行管理(獨(dú)立 JD、候選人池、向量索引隔離)和 HR 工作流(標(biāo)簽系統(tǒng)、分組展示、快速操作),同時(shí)進(jìn)行了架構(gòu)重構(gòu)(分層設(shè)計(jì)、數(shù)據(jù)分庫(kù)、模塊化),并增強(qiáng)了大模型分析輸出(四級(jí)推薦等級(jí)、結(jié)構(gòu)化優(yōu)劣勢(shì))和智能問(wèn)答(按崗位過(guò)濾檢索、流式輸出)。

這篇試圖說(shuō)清楚:

系統(tǒng)的實(shí)際效果演示、四層系統(tǒng)架構(gòu)拆解、五點(diǎn)核心技術(shù)實(shí)現(xiàn)、三個(gè)二次開(kāi)發(fā)場(chǎng)景指南,以及對(duì)端側(cè)模型應(yīng)用的一些感想。

1、視頻效果展示

在開(kāi)始講具體實(shí)現(xiàn)之前,老規(guī)矩先來(lái)看看整個(gè)系統(tǒng)的架構(gòu)設(shè)計(jì)。下面這張圖展示了從用戶(hù)界面到數(shù)據(jù)存儲(chǔ)的完整數(shù)據(jù)流。

圖片

2.1為啥要分四層

在上一版單崗位系統(tǒng)里,UI 代碼、業(yè)務(wù)邏輯、AI 調(diào)用全都混在一起的,一開(kāi)始寫(xiě)起來(lái)確實(shí)快,但這次升級(jí)到多崗位管理的時(shí)候,改動(dòng)起來(lái)難免顧此失彼,所以這次也算是做了下系統(tǒng)重構(gòu)。

前端交互層這部分依然采用了輕量化的 Streamlit,搭建了三個(gè) Tab 頁(yè)面:候選人概覽、候選人詳情和智能問(wèn)答。這一層只管顯示數(shù)據(jù)和響應(yīng)用戶(hù)操作,不關(guān)心數(shù)據(jù)從哪來(lái)以及怎么處理。

圖片

業(yè)務(wù)服務(wù)層這部分是整個(gè)系統(tǒng)的核心處理邏輯所在。一份簡(jiǎn)歷從上傳到展示,需要經(jīng)過(guò):先提取文本,再調(diào)用大模型分析,然后存入數(shù)據(jù)庫(kù),最后建立向量索引。這些流程編排都在ResumeProcessor這個(gè)類(lèi)里完成。還有一個(gè)PositionService,專(zhuān)門(mén)管理崗位的增刪改查。這一層的好處是,如果以后想改業(yè)務(wù)流程,比如在大模型分析前加一個(gè)簡(jiǎn)歷去重檢查,只需要在這一層加幾行代碼,不用動(dòng)其他地方。

核心引擎層這部分封裝了所有大模型相關(guān)的能力。這一層最重要的是RAGEngine,包括了簡(jiǎn)歷文本向量化、存入 ChromaDB,以及在用戶(hù)提問(wèn)時(shí)檢索相關(guān)內(nèi)容等功能。

數(shù)據(jù)存儲(chǔ)層這部分用了三種存儲(chǔ)方式:SQLite 存儲(chǔ)崗位信息和候選人結(jié)構(gòu)化數(shù)據(jù),ChromaDB 存儲(chǔ)向量索引,文件系統(tǒng)存儲(chǔ)原始簡(jiǎn)歷文件。所有復(fù)雜的 SQL 邏輯都封裝在各個(gè) Store 類(lèi)里,這樣以后如果要把 SQLite 換成 MySQL,只需要改 Store 類(lèi)的實(shí)現(xiàn),上層代碼完全不用動(dòng)。

2.2多崗位數(shù)據(jù)隔離

為了保證不同崗位的數(shù)據(jù)不會(huì)串臺(tái),我在三個(gè)層面做了隔離設(shè)計(jì)。

文件系統(tǒng)按崗位分目錄

最簡(jiǎn)單直接的辦法,就是把不同崗位的簡(jiǎn)歷文件分開(kāi)存。我在uploaded_resumes/目錄下,給每個(gè)崗位創(chuàng)建一個(gè)子目錄,目錄名就是崗位 ID。比如"AI 產(chǎn)品經(jīng)理"的崗位 ID 是position_001,這樣做的好處是刪除崗位的時(shí)候可以直接把整個(gè)目錄刪掉。

關(guān)系數(shù)據(jù)庫(kù)用外鍵關(guān)聯(lián)

在 SQLite 里,我給candidates表、ai_analysis表、hr_tags表都加上了position_id字段,并且設(shè)置了外鍵約束。每次查詢(xún)候選人數(shù)據(jù),SQL 語(yǔ)句里必須帶上WHERE position_id = ?,從源頭上避免跨崗位查詢(xún)。

向量數(shù)據(jù)庫(kù)按崗位分 collection

這是整個(gè)隔離機(jī)制里最關(guān)鍵的一環(huán),ChromaDB 支持創(chuàng)建多個(gè) collection(可以理解為不同的向量數(shù)據(jù)庫(kù)),我給每個(gè)崗位創(chuàng)建一個(gè)獨(dú)立的 collection。比如position_001對(duì)應(yīng)的 collection 叫resumes_position_001,position_002對(duì)應(yīng)的叫resumes_position_002。

可能會(huì)有人問(wèn)為啥不用 metadata 過(guò)濾呢?比如所有簡(jiǎn)歷存在一個(gè) collection 里,然后在 metadata 里標(biāo)記position_id,檢索的時(shí)候再過(guò)濾。這個(gè)方案看起來(lái)更簡(jiǎn)單,但有個(gè)致命問(wèn)題是,實(shí)際使用的時(shí)候性能會(huì)隨著候選人總數(shù)線性下降。假設(shè)系統(tǒng)里有 10 個(gè)崗位,每個(gè)崗位 100 個(gè)候選人,那全局就有 1000 個(gè)候選人的向量。每次檢索都要在 1000 個(gè)向量里搜索,然后再用 metadata 過(guò)濾,這無(wú)疑會(huì)很慢。

而用 collection 隔離,每個(gè)崗位的檢索只在自己那 100 個(gè)向量里進(jìn)行,性能只跟該崗位的候選人數(shù)相關(guān),跟其他崗位完全無(wú)關(guān)。這就是物理隔離優(yōu)于邏輯過(guò)濾的典型場(chǎng)景。這點(diǎn)非常像我在做 ibm rag 冠軍賽項(xiàng)目拆解中提到的“疑一文一庫(kù)”的做法。

2.3RAG 引擎與業(yè)務(wù)邏輯的解耦

在上一版系統(tǒng)里,我把 RAG 的代碼直接寫(xiě)在業(yè)務(wù)邏輯里,結(jié)果就是代碼復(fù)用性很差。這次重構(gòu)我專(zhuān)門(mén)抽出了一個(gè)通用的RAGEngine類(lèi),只提供三個(gè)核心能力:建索引、檢索、問(wèn)答。

業(yè)務(wù)層想用的時(shí)候,把數(shù)據(jù)準(zhǔn)備好,調(diào)用對(duì)應(yīng)的方法就行。比如ResumeProcessor在處理簡(jiǎn)歷的時(shí)候,會(huì)調(diào)用RAGEngine.build_index()把簡(jiǎn)歷文本向量化;在智能問(wèn)答的時(shí)候,會(huì)調(diào)用RAGEngine.query_with_rag()執(zhí)行 RAG 流程。

這種解耦帶來(lái)的好處是,如果各位想換一個(gè)向量數(shù)據(jù)庫(kù),比如從 ChromaDB 換成 Milvus,只需要改RAGEngine的內(nèi)部實(shí)現(xiàn),業(yè)務(wù)層的代碼一行都不用動(dòng)。或者如果想把這套 RAG 引擎用到其他項(xiàng)目,比如做一個(gè)標(biāo)準(zhǔn)的企業(yè)知識(shí)庫(kù)問(wèn)答系統(tǒng),直接復(fù)制core/rag_engine_v2.py這個(gè)文件過(guò)去,寫(xiě)一個(gè)新的 Processor 類(lèi)就能跑起來(lái)。

3、核心技術(shù)實(shí)現(xiàn)拆解

前面講完了架構(gòu)設(shè)計(jì),這部分從代碼層面講幾個(gè)關(guān)鍵的技術(shù)實(shí)現(xiàn),這部分會(huì)聚焦在私以為有一定工程借鑒價(jià)值的地方。

3.1簡(jiǎn)歷完整向量化的考量

一個(gè)好用的 RAG 系統(tǒng)設(shè)計(jì),一個(gè)繞不開(kāi)問(wèn)題是如何精準(zhǔn)的切分文檔。上一版系統(tǒng)里,因?yàn)槟康氖且菔就暾南到y(tǒng)流程,所以演示的簡(jiǎn)歷部分也是針對(duì)性的進(jìn)行了設(shè)計(jì),分塊部分按照"核心技能"、"工作經(jīng)歷"等章節(jié)標(biāo)題進(jìn)行處理。但這顯然不符合實(shí)際五花八門(mén)的簡(jiǎn)歷格式情況。 這次重構(gòu),我做了一個(gè)看似偷懶實(shí)則更務(wù)實(shí)的做法,就是把每份簡(jiǎn)歷作為一個(gè)完整的 node,不做分塊處理。

class MultiPositionNodeParser(NodeParser):
    """
    支持多崗位的簡(jiǎn)歷Node解析器(無(wú)分塊版本)


    設(shè)計(jì)策略:
    - 每份簡(jiǎn)歷作為一個(gè)完整的node,不進(jìn)行分塊
    - 在metadata中添加position_id用于多崗位隔離
    - 添加candidate_name用于候選人識(shí)別


    優(yōu)勢(shì):
    - 保留完整上下文,避免信息碎片化
    - 簡(jiǎn)歷通常很短,適合整體向量化
    - 檢索時(shí)一次性獲得候選人所有信息
    """


    def _parse_nodes(self, documents: List[Document], **kwargs) -> List[BaseNode]:
        all_nodes = []
        position_id = kwargs.get('position_id')


        # 按文件路徑分組(一個(gè)PDF可能有多頁(yè))
        docs_by_filepath = defaultdict(list)
        for doc in documents:
            docs_by_filepath[doc.metadata.get("file_path")].append(doc)


        for file_path, doc_parts in docs_by_filepath.items():
            # 合并所有頁(yè)面為完整文本
            doc_parts.sort(key=lambda d: int(d.metadata.get("page_label", "0")))
            full_text = "\n\n".join([d.get_content().strip() for d in doc_parts])


            # 創(chuàng)建包含整份簡(jiǎn)歷的node
            metadata = {
                "position_id": position_id,
                "candidate_name": Path(file_path).stem,
                "chunk_type": "full_resume",
                "resume_length": len(full_text)
            }


            node = TextNode(text=full_text, metadata=metadata)
            all_nodes.append(node)


        return all_nodes

首先有個(gè)共識(shí)是,簡(jiǎn)歷通常很短,一般 2-4 頁(yè),毛估 2000-4000 字左右,完全在主流嵌入模型的處理范圍內(nèi)。這次演示用的bge-m3模型支持 8192 tokens,處理一份完整簡(jiǎn)歷應(yīng)該說(shuō)毫無(wú)壓力。

其次,完整向量化也避免了信息碎片化。當(dāng) HR 問(wèn)“張三有沒(méi)有 RAG 項(xiàng)目經(jīng)驗(yàn)”的時(shí)候,如果簡(jiǎn)歷被切成 10 個(gè) chunk,可能需要檢索多個(gè) chunk 才能拼湊出完整答案。而整份簡(jiǎn)歷作為一個(gè) node,一次檢索就能拿到所有相關(guān)信息,LLM 可以看到完整上下文進(jìn)行推理。

再者,實(shí)現(xiàn)邏輯簡(jiǎn)單也減少了 bug 風(fēng)險(xiǎn)。代碼的核心邏輯就是把多頁(yè) PDF 合并成一個(gè)字符串,然后塞進(jìn)一個(gè) node,沒(méi)有復(fù)雜的正則匹配、沒(méi)有邊界判斷,代碼清晰好維護(hù)。在實(shí)際使用中實(shí)測(cè)效果很好,檢索準(zhǔn)確率明顯提升,而且及時(shí)是 8b 尺寸的量化模型推理過(guò)程也很穩(wěn)定。

3.2Pydantic 驅(qū)動(dòng)的結(jié)構(gòu)化分析

如何讓 LLM 穩(wěn)定地輸出結(jié)構(gòu)化數(shù)據(jù),這也是個(gè)繞不開(kāi)的挑戰(zhàn)。好的工程實(shí)踐,無(wú)非也是一套圍繞模型動(dòng)態(tài)邊界構(gòu)建的解決方案。這次我用了在歷史企業(yè)項(xiàng)目中常用的 Pydantic 模型 + 詳細(xì) Prompt 的組合,實(shí)現(xiàn)了相對(duì)可靠的結(jié)構(gòu)化輸出。先定義數(shù)據(jù)模型:

class AIResumeAnalysis(BaseModel):
    """AI簡(jiǎn)歷分析結(jié)果 - 定性分析 + 結(jié)構(gòu)化提取"""


    recommendation_level: str = Field(
        default="可考慮",
        descriptinotallow="推薦等級(jí): 強(qiáng)烈推薦 | 推薦 | 可考慮 | 不推薦"
    )


    key_strengths: List[str] = Field(
        default_factory=list,
        descriptinotallow="具體、可驗(yàn)證的優(yōu)勢(shì),優(yōu)先列出與崗位強(qiáng)相關(guān)的亮點(diǎn)"
    )


    key_concerns: List[str] = Field(
        default_factory=list,
        descriptinotallow="需要關(guān)注的方面,誠(chéng)實(shí)指出但不夸大"
    )


    one_sentence_summary: str = Field(
        default="",
        descriptinotallow="核心特征概括,幫助HR快速建立印象"
    )


    total_years_experience: int = Field(default=0)
    work_experience: List[WorkExperienceExtracted] = Field(default_factory=list)
    project_experience: List[ProjectExperienceExtracted] = Field(default_factory=list)


    # 字段校驗(yàn)器
    @field_validator('recommendation_level')
    @classmethod
    def validate_level(cls, v):
        valid_levels = ["強(qiáng)烈推薦", "推薦", "可考慮", "不推薦"]
        if v not in valid_levels:
            return "可考慮"
        return v


    @field_validator('key_strengths')
    @classmethod
    def validate_strengths_count(cls, v):
        if not v or len(v) == 0:
            return ["請(qǐng)查看簡(jiǎn)歷原文進(jìn)行人工評(píng)估"]
        return v[:5]  # 最多5條

Pydantic 的好處是,它不僅定義了數(shù)據(jù)結(jié)構(gòu),還內(nèi)置了校驗(yàn)邏輯。比如recommendation_level必須是四個(gè)等級(jí)之一,key_strengths至少有 1 條最多 5 條,這些規(guī)則會(huì)自動(dòng)執(zhí)行。然后在 Prompt 中明確輸出格式:

RESUME_ANALYSIS_PROMPT = """
你是一位專(zhuān)業(yè)的招聘顧問(wèn),請(qǐng)根據(jù)崗位描述和候選人簡(jiǎn)歷,輸出結(jié)構(gòu)化評(píng)估。
# 輸出要求
請(qǐng)嚴(yán)格按照以下 JSON 格式輸出,不要添加任何其他文字:
{
    "recommendation_level": "強(qiáng)烈推薦",
    "key_strengths": [
        "5年 AI 產(chǎn)品經(jīng)驗(yàn),主導(dǎo)過(guò) 3 個(gè) RAG 項(xiàng)目成功上線",
        "具備完整的 B 端產(chǎn)品設(shè)計(jì)能力(需求分析 → 原型設(shè)計(jì) → 上線運(yùn)營(yíng))"
    ],
    "key_concerns": [
        "缺乏制造業(yè)行業(yè)背景(現(xiàn)有經(jīng)驗(yàn)集中在互聯(lián)網(wǎng)行業(yè))"
    ],
    "one_sentence_summary": "技術(shù)型 AI 產(chǎn)品專(zhuān)家,RAG 項(xiàng)目經(jīng)驗(yàn)豐富,需補(bǔ)足制造業(yè)行業(yè)背景",
    ...
}
# 評(píng)估標(biāo)準(zhǔn)
### key_strengths(關(guān)鍵優(yōu)勢(shì))
- 輸出 3-5 條具體、可驗(yàn)證的優(yōu)勢(shì)
- 優(yōu)先列出與崗位強(qiáng)相關(guān)的亮點(diǎn)
- 盡量包含數(shù)據(jù)支撐(如"5年經(jīng)驗(yàn)"、"主導(dǎo)3個(gè)項(xiàng)目")
- 避免空洞描述(?"能力強(qiáng)" ?"5年AI產(chǎn)品經(jīng)驗(yàn),主導(dǎo)3個(gè)RAG項(xiàng)目")
...
"""

Prompt 里不僅給出了 JSON 格式,還詳細(xì)說(shuō)明了每個(gè)字段的要求,以及提供了好例子和壞例子的對(duì)比。這種高指令性的 Prompt 實(shí)測(cè)可以明顯提升 LLM 輸出的質(zhì)量和穩(wěn)定性。最后在解析時(shí)做好容錯(cuò)處理:

def _parse_response(self, response_text: str) -> AIResumeAnalysis:
    """解析LLM響應(yīng)為AIResumeAnalysis對(duì)象"""
    try:
        # 嘗試1:直接解析
        data = json.loads(response_text)
        return AIResumeAnalysis(**data)
    except json.JSONDecodeError:
        # 嘗試2:提取JSON塊
        json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
        if json_match:
            data = json.loads(json_match.group(0))
            return AIResumeAnalysis(**data)


        # 嘗試3:清理后解析(移除markdown代碼塊等)
        cleaned = self._clean_json_text(response_text)
        if cleaned:
            data = json.loads(cleaned)
            return AIResumeAnalysis(**data)


    # 降級(jí)策略:返回默認(rèn)值
    return self._get_fallback_analysis()

這套機(jī)制總結(jié)來(lái)說(shuō)有三層容錯(cuò):先嘗試直接解析,不行就用正則提取 JSON 塊,再不行就清理格式后解析,最后還有一個(gè)降級(jí)策略返回默認(rèn)值。實(shí)際使用中,絕大部分情況第一次就能成功解析,少數(shù)格式異常的也能被后續(xù)邏輯兜住,系統(tǒng)健壯性非常高。

3.3簡(jiǎn)歷處理的五步流水線

一份簡(jiǎn)歷從上傳到最終可用,需要經(jīng)歷多個(gè)步驟。原版系統(tǒng)這些邏輯散落在各處,這次我用ResumeProcessor類(lèi)把整個(gè)流程標(biāo)準(zhǔn)化成五步流水線:

def process_uploaded_file(self, uploaded_file, position_id, job_description):
    """處理上傳的簡(jiǎn)歷文件(完整流程)"""


    result = {
        'steps': {
            'extract': False,   # 步驟1:文本提取
            'validate': False,  # 步驟2:內(nèi)容驗(yàn)證
            'analyze': False,   # 步驟3:AI分析
            'index': False,     # 步驟4:向量索引
            'save': False       # 步驟5:保存數(shù)據(jù)
        }
    }


    # 步驟1:文本提取
    text, extract_success = extract_text_from_file(temp_path)
    result['steps']['extract'] = extract_success
    if not extract_success:
        return False, f"? 文件解析失敗", result


    # 步驟2:內(nèi)容驗(yàn)證
    is_valid = validate_resume_content(text)
    result['steps']['validate'] = is_valid
    if not is_valid:
        return False, f"?? 文件內(nèi)容疑似不是簡(jiǎn)歷", result


    # 步驟3:AI分析
    analysis, analyze_success = self.analyzer.analyze(text, job_description)
    result['steps']['analyze'] = analyze_success


    # 步驟4:向量索引
    index_success = self.rag_engine.ingest_resume(temp_path, position_id)
    result['steps']['index'] = index_success


    # 步驟5:保存到數(shù)據(jù)庫(kù)
    profile = self._create_candidate_profile(...)
    candidate_id = self.candidate_store.save(profile, analysis)
    result['steps']['save'] = candidate_id > 0


    return True, "? 處理成功", result

這個(gè)設(shè)計(jì)有幾個(gè)好處:

首先,每一步都有明確的輸入輸出和成功標(biāo)志。result['steps']字典記錄了每一步的執(zhí)行狀態(tài),方便調(diào)試和監(jiān)控。如果處理失敗,可以立刻看到是在哪一步出的問(wèn)題。

其次,失敗快速返回,避免無(wú)效計(jì)算。如果文本提取就失敗了,后面的大模型分析、向量索引都不用做了,直接返回錯(cuò)誤信息。這種 fail-fast 策略節(jié)省資源,也讓錯(cuò)誤信息更清晰。

最后,result字典不僅包含每一步的狀態(tài),還包含候選人 ID、分析結(jié)果、文件路徑等信息,上層 UI 可以根據(jù)這些信息給用戶(hù)精準(zhǔn)的反饋。

這種流水線模式在企業(yè)級(jí)應(yīng)用中非常常見(jiàn),它把復(fù)雜流程拆解成清晰的步驟,每步職責(zé)單一,容易測(cè)試和維護(hù)。

3.4多崗位檢索的元數(shù)據(jù)過(guò)濾機(jī)制

前面架構(gòu)部分提到,我用 metadata 過(guò)濾實(shí)現(xiàn)多崗位隔離。這里展示一下具體的檢索代碼:

def retrieve(self, query: str, position_id: int = None, top_k: int = 5):
    """檢索相關(guān)文檔節(jié)點(diǎn)"""


    # 構(gòu)建metadata過(guò)濾器
    filters = None
    if position_id is not None:
        filters = MetadataFilters(
            filters=[
                MetadataFilter(
                    key="position_id",
                    value=position_id,
                    operator=FilterOperator.EQ
                )
            ]
        )


    # 創(chuàng)建檢索器
    retriever = self.index.as_retriever(
        similarity_top_k=top_k,
        filters=filters
    )


    # 執(zhí)行檢索
    retrieved_nodes = retriever.retrieve(query)
    return retrieved_nodes

LlamaIndex 的MetadataFilters功能非常強(qiáng),支持多種操作符(EQ、GT、LT、IN等),可以組合多個(gè)條件。這里我只用了最簡(jiǎn)單的等值匹配,但如果以后需要更復(fù)雜的查詢(xún),比如"工作年限大于 5 年且有制造業(yè)背景",只需要添加更多的MetadataFilter就行。在向量化這一步,我把position_id、candidate_name等信息存入 node 的 metadata:

metadata = {
    "position_id": position_id,
    "candidate_name": Path(file_path).stem,
    "chunk_type": "full_resume",
    "resume_length": len(full_text)
}
node = TextNode(text=full_text, metadata=metadata)

檢索的時(shí)候,ChromaDB 會(huì)先在全局向量空間找到語(yǔ)義最相似的 top-K 個(gè)結(jié)果,然后用 metadata 過(guò)濾器篩選出符合條件的 node。語(yǔ)義匹配 + 精確過(guò)濾的組合,既保證了檢索質(zhì)量,又實(shí)現(xiàn)了數(shù)據(jù)隔離。

需要注意的是,我在架構(gòu)設(shè)計(jì)部分提到過(guò) collection 隔離性能更好。但實(shí)際開(kāi)發(fā)時(shí)我發(fā)現(xiàn) LlamaIndex 對(duì)多 collection 管理不太友好,需要為每個(gè)崗位創(chuàng)建獨(dú)立的 index 對(duì)象,代碼會(huì)變得很復(fù)雜。所以我在單 collection + metadata 過(guò)濾和多 collection 隔離之間做了權(quán)衡,選擇了前者。

這也是說(shuō)明了理論最優(yōu)方案不一定是工程最優(yōu)方案。要考慮框架限制、開(kāi)發(fā)成本、可維護(hù)性等多方面因素。目前這個(gè)方案在候選人數(shù)量不超過(guò) 200 的情況下性能完全夠用,如果以后數(shù)據(jù)量真的上去了,再重構(gòu)成多 collection 也不遲。

3.5基于 Session 的多輪對(duì)話記憶

智能問(wèn)答如果只能單輪回答,用戶(hù)體驗(yàn)無(wú)疑會(huì)很差。我用 Streamlit 的session_state實(shí)現(xiàn)了按崗位隔離的對(duì)話記憶。

def render_chat_tab(position_id: int, position_name: str, ...):
    """渲染智能問(wèn)答Tab"""


    # 初始化聊天歷史 - 每個(gè)崗位獨(dú)立的對(duì)話記憶
    chat_key = f"chat_history_{position_id}"
    if chat_key not in st.session_state:
        st.session_state[chat_key] = []


    # 顯示歷史消息
    recent_messages = st.session_state[chat_key][-15:]  # 只顯示最近15條
    for message in recent_messages:
        with st.chat_message(message["role"]):
            st.markdown(message["content"])


    # 用戶(hù)輸入
    if prompt := st.chat_input("請(qǐng)?jiān)诖溯斎肽膯?wèn)題..."):
        # 添加用戶(hù)消息到歷史
        st.session_state[chat_key].append({"role": "user", "content": prompt})


        # ... 執(zhí)行RAG檢索和LLM推理 ...


        # 保存AI回答到歷史
        st.session_state[chat_key].append({
            "role": "assistant",
            "content": answer_content,
            "think_content": think_content  # 推理過(guò)程
        })

首先是按崗位 ID 隔離對(duì)話歷史。不同崗位的問(wèn)答互不干擾,HR 在"AI 產(chǎn)品經(jīng)理"崗位的對(duì)話,不會(huì)影響到"算法工程師"崗位。其次,只顯示最近 N 條消息。對(duì)話歷史完整保存在session_state里,但頁(yè)面上只顯示最近 15 條,避免頁(yè)面過(guò)長(zhǎng)影響體驗(yàn)。

圖片

最后,保存推理過(guò)程。除了最終答案,我還把 LLM 的 thinking 過(guò)程保存下來(lái)。這樣用戶(hù)可以在st.expander里查看 AI 的推理鏈路,提升可解釋性。這個(gè)實(shí)現(xiàn)雖然簡(jiǎn)單,但在實(shí)際使用中效果很好。多輪對(duì)話讓 HR 可以不斷追問(wèn),獲得更深入的候選人情況了解。

4、二次開(kāi)發(fā)指南

在實(shí)際使用中,不同企業(yè)或者用戶(hù)會(huì)有不同的定制需求。這部分我根據(jù)和一些從業(yè)者的溝通,介紹三個(gè)高頻需求場(chǎng)景,作為二次開(kāi)發(fā)的參考。

4.1批量導(dǎo)入簡(jiǎn)歷

從招聘網(wǎng)站批量下載的簡(jiǎn)歷通常打包在 zip 文件里,每次都要手動(dòng)解壓再上傳,非常低效。如果能直接上傳 zip 包,系統(tǒng)自動(dòng)解壓并批量處理,能節(jié)省掉不必要的手動(dòng)操作。

# 核心邏輯:解壓zip并批量處理
import zipfile
from pathlib import Path
if uploaded_file.name.endswith('.zip'):
    # 解壓到臨時(shí)目錄
    with zipfile.ZipFile(uploaded_file) as zip_ref:
        zip_ref.extractall('./temp_extract')


    # 掃描所有簡(jiǎn)歷文件
    resume_files = list(Path('./temp_extract').rglob('*.pdf')) + \
                   list(Path('./temp_extract').rglob('*.docx'))


    # 調(diào)用現(xiàn)有的批量處理
    processor.batch_process(resume_files, position_id, job_description)

核心實(shí)現(xiàn)思路是在上傳組件支持 zip 格式,解壓后遞歸掃描所有簡(jiǎn)歷文件,然后調(diào)用現(xiàn)有的批量處理邏輯即可。

4.2薪資期望范圍提取與預(yù)算匹配

薪資是招聘決策中的關(guān)鍵因素,但人工查看每份簡(jiǎn)歷判斷是否在預(yù)算內(nèi),無(wú)疑會(huì)浪費(fèi)大量時(shí)間在預(yù)算不匹配的候選人上。如果系統(tǒng)能自動(dòng)提取薪資期望并與崗位預(yù)算對(duì)比,就可以在篩選階段就過(guò)濾掉不合適的候選人。

# 1. 擴(kuò)展數(shù)據(jù)模型
class AIResumeAnalysis(BaseModel):
    # ... 原有字段 ...
    expected_salary_min: int = Field(default=0, descriptinotallow="期望月薪下限(K)")
    expected_salary_max: int = Field(default=0, descriptinotallow="期望月薪上限(K)")
# 2. 在Prompt中增加提取指令(analysis_prompts.py)
# "從簡(jiǎn)歷中提取薪資期望,統(tǒng)一轉(zhuǎn)為月薪,如'20-25K'或'年薪30萬(wàn)'→25K"
# 3. 在UI中顯示匹配狀態(tài)
if candidate.expected_salary_max <= position.salary_budget_max:
    st.success(f"? 預(yù)算內(nèi) ({candidate.expected_salary_min}-{candidate.expected_salary_max}K)")

核心實(shí)現(xiàn)思路,是在大模型分析的 Pydantic 模型中增加薪資字段,在 Prompt 中增加提取指令,大模型會(huì)自動(dòng)識(shí)別各種薪資表述("20-25K"、"年薪 30 萬(wàn)"等)并歸一化為統(tǒng)一格式。

4.3AI 生成面試問(wèn)題清單

篩選出候選人后需要準(zhǔn)備面試問(wèn)題,要仔細(xì)讀簡(jiǎn)歷找出可以深挖的點(diǎn)。如果系統(tǒng)能根據(jù)簡(jiǎn)歷和崗位要求自動(dòng)生成針對(duì)性的面試問(wèn)題,可以大幅提升面試準(zhǔn)備效率。

# 生成面試問(wèn)題的核心Prompt
PROMPT = """基于簡(jiǎn)歷和崗位要求,生成面試問(wèn)題:
- 能力驗(yàn)證: 驗(yàn)證簡(jiǎn)歷中的技能是否真實(shí)
- 項(xiàng)目深挖: 了解關(guān)鍵項(xiàng)目的實(shí)際貢獻(xiàn)
- 短板確認(rèn): 針對(duì)"{key_concerns}"提問(wèn)
輸出JSON: {{"ability": [...], "project": [...], "weakness": [...]}}
"""
# 調(diào)用LLM生成
response = llm.complete(PROMPT.format(key_cnotallow=candidate.ai_concerns))
questions = json.loads(response.text)
# 在詳情頁(yè)展示
st.subheader("?? AI生成的面試問(wèn)題")
for category, qs in questions.items():
    for q in qs:
        st.write(f"? {q}")

核心實(shí)現(xiàn)思路是設(shè)計(jì)一個(gè)專(zhuān)門(mén)的 Prompt,讓 LLM 基于簡(jiǎn)歷內(nèi)容和分析結(jié)果,生成分類(lèi)的面試問(wèn)題(能力驗(yàn)證、項(xiàng)目深挖、短板確認(rèn)等)。

5、寫(xiě)在最后

現(xiàn)在,當(dāng)大家談?wù)摯竽P推髽I(yè)應(yīng)用落地的時(shí)候,默認(rèn)的潛臺(tái)詞都是企業(yè)主導(dǎo)、集中部署。而現(xiàn)實(shí)情況是,企業(yè)去落地一個(gè)面向于不同部門(mén)的大模型應(yīng)用,是一個(gè)道阻且長(zhǎng)的過(guò)程。但實(shí)際上像 HR、律師、會(huì)計(jì)這類(lèi)專(zhuān)業(yè)工作者,每天也在做大量重復(fù)勞動(dòng)。如果能把這套系統(tǒng)打包成一個(gè)開(kāi)箱即用的桌面應(yīng)用,內(nèi)置嵌入模型、向量數(shù)據(jù)庫(kù)、開(kāi)源 LLM,完全本地運(yùn)行,不依賴(lài)云服務(wù),可以更加短平快的的給很多崗位帶來(lái)提效或者解放雙手。

這個(gè)項(xiàng)目中演示時(shí)使用的 qwen3:8b(Q4 量化版)5.2GB大小,在我的 24GB 內(nèi)存 MacBook 上,首字響應(yīng)時(shí)間大概 5 秒,后續(xù) token 生成速度相對(duì)比較流暢。但這個(gè)性能對(duì)大部分 HR 的工作電腦來(lái)說(shuō)是個(gè)不小的挑戰(zhàn)。DeepSeek 年初開(kāi)源的蒸餾后的小尺寸模型,在智力水平和電腦性能要求上都不夠?qū)嵱茫F(xiàn)在似乎重新來(lái)到了端側(cè)模型應(yīng)用重新爆發(fā)的臨界點(diǎn)。一方面,1.5B-3B 的小模型配合垂直領(lǐng)域微調(diào),完全可以勝任簡(jiǎn)歷篩選這類(lèi)專(zhuān)業(yè)場(chǎng)景。其次,更優(yōu)秀的小尺寸開(kāi)源模型更新的速度我想也會(huì)超出大家的預(yù)期。

從創(chuàng)業(yè)視角來(lái)看,這不僅是 2B 市場(chǎng)的機(jī)會(huì),更是面向?qū)I(yè)個(gè)人用戶(hù)(像 Cursor 面向開(kāi)發(fā)者那樣)的增量市場(chǎng)。讓專(zhuān)業(yè)工作者都能擁有自己的大模型應(yīng)用助手,而不是一味的等待企業(yè)采購(gòu),這或許是接下來(lái)非常值得保持關(guān)注的長(zhǎng)尾需求。

責(zé)任編輯:龐桂玉 來(lái)源: 韋東東
相關(guān)推薦

2025-09-29 02:00:00

2025-01-15 08:01:45

2024-10-15 11:04:18

2020-09-15 07:00:00

SaaS架構(gòu)架構(gòu)

2025-01-09 14:39:40

2022-07-08 08:07:14

SpringJavaMail

2023-03-09 09:31:58

架構(gòu)設(shè)計(jì)vivo

2015-08-12 15:46:02

SaaS多租戶(hù)數(shù)據(jù)存儲(chǔ)

2015-04-02 11:04:27

云應(yīng)用SaaSOFBIZ

2020-10-16 08:57:51

云平臺(tái)之多租戶(hù)的實(shí)踐

2025-06-11 10:00:00

多繼承MixinPython

2025-09-26 02:15:00

2017-06-10 11:13:39

數(shù)據(jù)庫(kù)架構(gòu)數(shù)據(jù)庫(kù)集群

2025-05-14 03:00:00

2013-11-26 17:29:43

思科呼叫中心多租戶(hù)

2010-06-11 14:55:20

2022-01-10 08:17:40

異地設(shè)計(jì)實(shí)踐

2025-05-26 09:49:59

多模態(tài)智能體RAG

2023-06-07 13:50:00

SaaS多租戶(hù)系統(tǒng)

2025-11-05 01:00:00

架構(gòu)業(yè)務(wù)系統(tǒng)MVC
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

日韩五码在线观看| 国产欧美丝袜| 成年人一级黄色片| 国内自拍欧美| 91福利社在线观看| 超薄肉色丝袜足j调教99| 人妻无码中文字幕免费视频蜜桃| 蜜桃av综合| 久久视频在线直播| www.超碰97| 成人在线精品| 日本高清不卡在线观看| 可以在线看黄的网站| 天堂av在线7| 国产中文字幕一区| 9.1国产丝袜在线观看| 亚洲一级理论片| 女同另类激情重口| 777xxx欧美| 国产视频一区二区三区在线播放 | 欧美精品一二区| 中文字幕在线观看的网站| 成人永久在线| 欧美日韩精品欧美日韩精品一 | 精品国产一区二区三区四区四| 一本大道熟女人妻中文字幕在线 | 精品久久久久久久久久ntr影视 | 在线āv视频| 国产蜜臀97一区二区三区| 国产精品免费一区二区三区| 91精品国产乱码久久| 久久精品三级| 97在线视频免费观看| 中文字幕手机在线观看| 久久美女视频| 亚洲亚裔videos黑人hd| 变态另类丨国产精品| 99这里只有精品视频| 制服丝袜在线91| 日本人视频jizz页码69| 成人天堂yy6080亚洲高清| 精品成人av一区| 日本一本中文字幕| 羞羞的视频在线看| 亚洲激情图片小说视频| 欧美 另类 交| 国产在线观看av| 国产精品污污网站在线观看| 奇米精品在线| 国产高清在线| 亚洲国产精品黑人久久久| 欧洲精品码一区二区三区免费看| 亚洲av激情无码专区在线播放| 成人一区二区三区在线观看| 成人精品一二区| 成人福利小视频| 国产成人在线影院| 国产精品国产精品国产专区不卡| 国产高清第一页| 国产成人亚洲精品青草天美| 99国精产品一二二线| 亚洲成人久久精品| 国产成人av电影在线播放| 91香蕉亚洲精品| 99国产精品久久久久99打野战| 国产精品综合在线视频| 99在线视频播放| 日韩有码第一页| 97超碰欧美中文字幕| 欧美亚洲免费高清在线观看| 精品视频一二区| 国产精品国产三级国产有无不卡| 中文字幕在线亚洲三区| 影音先锋在线视频| 亚洲第一搞黄网站| 免费观看精品视频| 国产成人午夜性a一级毛片| 欧美人妇做爰xxxⅹ性高电影| 在线观看免费的av| 97一区二区国产好的精华液| 日韩高清中文字幕| 网爆门在线观看| 欧美不卡视频| 欧美一区第一页| 在线观看中文字幕2021| 高清成人在线观看| 欧美日韩国产精品一区二区| 日本在线看片免费人成视1000| 亚洲欧美国产毛片在线| 欧美亚洲日本一区二区三区| 最新欧美电影| 日韩欧美在线综合网| 中文字幕丰满孑伦无码专区| 天天影视欧美综合在线观看| 隔壁老王国产在线精品| 波多野结衣爱爱| 国产精品亚洲一区二区三区妖精| 久久久久网址| 99自拍视频在线观看| 精品福利一区二区| 国产成人在线综合| 在线成人动漫av| 欧美成人一二三| 欧产日产国产69| 国产电影一区二区三区| 天堂社区 天堂综合网 天堂资源最新版| 97超碰资源站在线观看| 日韩欧中文字幕| www.四虎精品| 国产精品久久久久久久久妇女| 77777亚洲午夜久久多人| 91久久久久国产一区二区| av在线不卡免费看| 91视频成人免费| 亚洲成人va| 亚洲另类图片色| 久久久久久国产精品视频| 蜜桃精品视频在线| 欧美精品一区二区视频| 2020av在线| 日韩三级高清在线| 国产免费久久久久| 日韩精品亚洲专区| 精品网站在线看| 2020国产在线| 精品久久人人做人人爰| 亚洲精品卡一卡二| 久久电影国产免费久久电影 | 日韩理论在线| 国产91九色视频| 三级av在线| 亚洲国产成人porn| 操人视频免费看| 一区二区三区在线| 国产主播在线一区| av资源网站在线观看| 欧美性极品xxxx做受| 喷水视频在线观看| 中文日韩欧美| 精品久久久久久亚洲| 国精产品一区一区三区mba下载| 91精品国产综合久久精品性色 | 亚洲国产三级在线| 潘金莲一级淫片aaaaaaa| 亚洲九九视频| 亚洲xxxx做受欧美| 亚洲夜夜综合| 日韩欧美激情在线| 欧美成人黄色网| 国产99一区视频免费| 国产一二三区在线播放| 亚洲视频国产精品| 欧美极品欧美精品欧美视频 | **国产精品| 久久亚洲精品一区| 国产片高清在线观看| 亚洲欧美电影院| 日本成人在线免费| 伊人久久成人| 久久这里精品国产99丫e6| 松下纱荣子在线观看| 亚洲欧美中文日韩v在线观看| 一级片免费在线播放| 国产欧美综合色| 国产精品一区二区羞羞答答| 久久综合国产| 91精品国产一区二区三区动漫| 新版中文在线官网| 亚洲精品成人免费| 在线观看你懂的网站| 国产精品你懂的在线欣赏| www.色欧美| 亚洲视屏一区| 欧美日本韩国一区二区三区| 日韩综合av| 久久久噜噜噜久噜久久| 国内av一区二区三区| 欧美日本在线视频| 青青草手机视频在线观看| 91在线视频18| 中文字幕在线视频精品| 精品91在线| 日韩免费av电影| 国产在线不卡一区二区三区| 高清欧美性猛交xxxx黑人猛交| 猫咪在线永久网站| 91精品蜜臀在线一区尤物| 国产一级av毛片| 国产亚洲欧美在线| 中文字幕一二三区| 久久综合九色综合欧美狠狠| 国产精品波多野结衣| 女一区二区三区| 成人精品久久一区二区三区| 国产精品www爽爽爽| 亚洲精品a区| 男人天堂手机在线观看| 一本色道久久综合亚洲aⅴ蜜桃| 亚洲综合第一区| 成人高清在线视频| 伊人色在线观看| 亚洲永久免费精品| 国内外成人激情免费视频| 精品一区欧美| 国产精品成人一区二区三区| 草莓视频成人appios| 久久久久国产精品免费| 日本中文字幕在线播放| 日韩hd视频在线观看| 国产视频一二三四区| 在线免费观看日本一区| 黄色激情视频在线观看| 最新日韩在线视频| 欧美人与性囗牲恔配| 成人妖精视频yjsp地址| 精品综合久久久久| 日韩国产欧美在线视频| 日韩精品―中文字幕| 午夜欧美视频| 日本不卡一区二区三区四区| 少妇精品久久久一区二区三区| 成人自拍偷拍| 日韩精品一区二区三区中文在线| 国产精品一区专区欧美日韩| 日本久久免费| 欧美在线一区二区三区四| 岛国片av在线| 久久久亚洲福利精品午夜| h片在线免费| 久久电影一区二区| 日本福利在线| 一色桃子一区二区| 深夜福利在线观看直播| 亚洲国产精彩中文乱码av在线播放| 精品国自产在线观看| 欧美日韩国产天堂| 一级爱爱免费视频| 精品视频全国免费看| 中国女人真人一级毛片| 欧美亚洲一区二区在线观看| 欧美日韩综合一区二区三区| 日韩欧美在线字幕| 人人草在线观看| 在线视频一区二区免费| 亚洲 欧美 日韩 在线| 日韩欧亚中文在线| 免费又黄又爽又猛大片午夜| 欧美影院精品一区| 中国女人一级一次看片| 欧美美女一区二区三区| ,一级淫片a看免费| 欧美一区2区视频在线观看| 国产av无码专区亚洲a∨毛片| 欧美一级黄色片| 高潮毛片7777777毛片| 亚洲国产女人aaa毛片在线| 天天干视频在线观看| 精品视频www| 国产香蕉视频在线看| 日韩中文字幕视频在线观看| 国产美女av在线| 欧美区在线播放| 国产91足控脚交在线观看| 午夜剧场成人观在线视频免费观看| 欧美办公室脚交xxxx| 国产精品91在线| 日本午夜免费一区二区| 97超碰人人看人人 | 在线免费看av网站| 亚洲视频一区在线观看| 国产大片中文字幕| 富二代精品短视频| 亚洲视屏在线观看| 日韩一区二区三区免费观看| 日日躁夜夜躁白天躁晚上躁91| 亚洲高清色综合| 国产在线自天天| 久久av中文字幕| 性欧美freesex顶级少妇| 国产精品久久久久久网站| 亚洲午夜剧场| 久久精品国产理论片免费| 日本一区二区三区视频| 激情六月天婷婷| 久久综合图片| 九九九久久久久久久| 久久色在线观看| 亚洲色图综合区| 一本到高清视频免费精品| 国产乱人乱偷精品视频| 日韩成人在线免费观看| 超碰在线观看免费版| 1769国产精品| 天堂av一区| 日韩在线电影一区| 激情欧美日韩一区| 邪恶网站在线观看| 99re热视频精品| 久草视频手机在线| 91久久国产综合久久| 亚洲国产欧美另类| 中文字幕日韩在线观看| 免费在线中文字幕| 91精品久久久久久久久久久| 欧美电影免费网站| 国产一区二区三区播放| 免费在线看一区| 美女又爽又黄视频毛茸茸| 亚洲日穴在线视频| 中文字幕日韩第一页| 亚洲九九九在线观看| 伊人精品影院| 成人午夜在线影院| 国产精品欧美日韩一区| 老太脱裤让老头玩ⅹxxxx| 国产精品一二三四五| 殴美一级黄色片| 日本乱人伦一区| 四虎影院在线播放| 欧美精品久久久久久久久| 国产一区二区在线观| 在线观看福利一区| 青青草原综合久久大伊人精品优势 | 日韩视频中文字幕在线观看| 欧美色区777第一页| 久热av在线| 欧美亚洲视频一区二区| 国产人妖ts一区二区| 8x8ⅹ国产精品一区二区二区| 美女性感视频久久| 免费成人深夜天涯网站| 欧美亚洲国产一区在线观看网站| 免费福利在线观看| 欧洲永久精品大片ww免费漫画| 动漫3d精品一区二区三区乱码| 成年人视频网站免费| 国产成人久久精品77777最新版本 国产成人鲁色资源国产91色综 | 久久亚洲资源中文字| 日韩欧美99| 日韩av在线播放中文字幕| 日韩精品电影一区二区| 一本色道综合亚洲| 国产福利小视频在线观看| 国产精品福利在线观看| 成人6969www免费视频| 天堂中文视频在线| 中文一区一区三区高中清不卡| 中国一级特黄视频| 久久中文字幕视频| 国产成人视屏| 波多野结衣与黑人| 成人一区二区三区中文字幕| 日产精品久久久久久久| 国产午夜精品理论片a级探花| 日韩影片中文字幕| 亚洲精品国产精品国自产| 久久国内精品自在自线400部| 国产美女高潮视频| 日韩一区二区在线观看视频| 欧美aaaaaaa| 久久99欧美| 日本不卡一区二区三区| 久久噜噜色综合一区二区| 欧美一区二区三区免费视频| 午夜av在线播放| 久久久国产精品一区二区三区| 日韩国产欧美三级| 男人的午夜天堂| 亚洲电影第1页| xxxxxx欧美| 亚洲欧美日韩精品综合在线观看 | 91免费国产视频| 亚洲一级二级| 无码人妻精品一区二区中文| 欧美日韩国产综合一区二区 | 日韩av大片在线| 天天做天天爱综合| 成人在线电影网站| 在线免费观看日本一区| a级片国产精品自在拍在线播放| 国产精品久久国产精品| 日本午夜精品一区二区三区电影| 欧美激情 一区| 精品日韩99亚洲| 午夜不卡影院| 一级做a爰片久久| 精品夜夜嗨av一区二区三区| 国产精品第56页| 一本一本久久a久久精品牛牛影视 一本色道久久综合亚洲精品小说 一本色道久久综合狠狠躁篇怎么玩 | 亚洲国产高潮在线观看| 四虎4545www国产精品| 国产a级黄色大片| 国产午夜精品一区二区三区视频| 99免费在线视频| 国产精品国产三级国产aⅴ浪潮| 欧美在线黄色| 欧美激情亚洲色图| 亚洲第一福利网| 成人免费91| 国产精品入口免费软件|