From e1b8366eccb8f5d0150e1c9d00bf7b323acfae2d Mon Sep 17 00:00:00 2001 From: jay77721 <164177721+jay77721@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:47:37 +0800 Subject: [PATCH] refactor: remove resume (CV) workflow DSL template and Go handling code (#18177) --- .../cv_analysis_and_candidate_evaluation.json | 428 ---------------- internal/agent/dsl/testdata/resume.json | 461 ------------------ internal/ingestion/component/extractor.go | 7 +- .../ingestion/component/extractor_test.go | 45 -- internal/ingestion/component/slice3_test.go | 2 +- 5 files changed, 3 insertions(+), 940 deletions(-) delete mode 100644 agent/templates/cv_analysis_and_candidate_evaluation.json delete mode 100644 internal/agent/dsl/testdata/resume.json diff --git a/agent/templates/cv_analysis_and_candidate_evaluation.json b/agent/templates/cv_analysis_and_candidate_evaluation.json deleted file mode 100644 index 3e175f555d..0000000000 --- a/agent/templates/cv_analysis_and_candidate_evaluation.json +++ /dev/null @@ -1,428 +0,0 @@ - -{ - "id": 15, - "title": { - "en": "CV Analysis and Candidate Evaluation", - "de": "Lebenslaufanalyse und Kandidatenbewertung", - "zh": "简历分析和候选人评估"}, - "description": { - "en": "This is a workflow that helps companies evaluate resumes, HR uploads a job description first, then submits multiple resumes via the chat window for evaluation.", - "de": "Dies ist ein Workflow, der Unternehmen bei der Bewertung von Lebensläufen hilft. Die Personalabteilung lädt zunächst eine Stellenbeschreibung hoch und reicht dann mehrere Lebensläufe über das Chat-Fenster zur Bewertung ein.", - "zh": "帮助公司评估简历的工作流。HR首先上传职位描述,通过聊天窗口提交多份简历进行评估。"}, - "canvas_type": "Other", - "dsl": { - "components": { - "Agent:AfraidBearsShare": { - "downstream": [ - "Message:TenLizardsShake" - ], - "obj": { - "component_name": "Agent", - "params": { - "delay_after_error": 1, - "description": "", - "exception_comment": "", - "exception_default_value": "", - "exception_goto": [], - "exception_method": null, - "frequencyPenaltyEnabled": false, - "frequency_penalty": 0.5, - "llm_id": "", - "maxTokensEnabled": false, - "max_retries": 1, - "max_rounds": 1, - "max_tokens": 4096, - "mcp": [], - "message_history_window_size": 12, - "outputs": { - "content": { - "type": "string", - "value": "" - } - }, - "parameter": "Precise", - "presencePenaltyEnabled": false, - "presence_penalty": 0.5, - "prompts": [ - { - "content": "HR is asking about: {sys.query}\n\nJob description is {begin@JD}\n\nResume is {IterationItem:EagerGiftsOpen@item}", - "role": "user" - } - ], - "sys_prompt": "# HR Resume Batch Processing Agent \n\n## Mission Statement\n\nYou are a professional HR resume processing agent designed to handle large-scale resume screening . Your primary goal is to extract standardized candidate information and provide efficient JD matching analysis in a clear, hierarchical text format.And always use Chinese to answer questions, and always separate each resume information with paragraphs.\n\n## Core Capabilities\n\n### 1. Standardized Information Extraction\n\n- Extract 6 key data points from each resume\n\n\n- Normalize all information to consistent format\n\n- Ensure data quality and completeness\n\n- Provide confidence levels for extracted information\n\n### 3. JD Matching Analysis\n\n1. Score: [X/10] \n\n2. Matching Analysis: \n\n- Clearly state the main points of alignment between resume and job description. \n\n- Mention any strong matches in experience, skills, or education. \n\n- Indicate if there are any gaps or mismatches. \n\n\n\n- Content length must always be between 30-50 characters\n\n### Output Specifications\n\n\n\n\n**Important requirement**: No subheadings\n\n\n\n- Full name without titles\n\n- Primary phone/email in standard format\n\n- Most recent educational institution\n\n- Numeric value (years of experience or graduation year)\n\n- Current residence city only\n\n- JD Matching Analysis\n\n\n## Processing Workflow\n\n### Step 1: File Analysis\n\n### Step 2: Information Extraction\n\n### Step 3: JD Matching Analysis\n\n### Step 4: Text Formatting\n\n### Step 5: Output complete context\uff08Strictly keep one line per message and do not merge. The content of the second resume and the previous resume are not allowed to be on the same line\uff09", - "temperature": "0.1", - "temperatureEnabled": true, - "tools": [], - "topPEnabled": false, - "top_p": 0.75, - "user_prompt": "", - "visual_files_var": "" - } - }, - "parent_id": "Iteration:PetiteBanksWarn", - "upstream": [ - "IterationItem:EagerGiftsOpen" - ] - }, - "Iteration:PetiteBanksWarn": { - "downstream": [], - "obj": { - "component_name": "Iteration", - "params": { - "items_ref": "sys.files", - "outputs": { - "evaluation": { - "ref": "Agent:AfraidBearsShare@content", - "type": "Array" - } - } - } - }, - "upstream": [ - "begin" - ] - }, - "IterationItem:EagerGiftsOpen": { - "downstream": [ - "Agent:AfraidBearsShare" - ], - "obj": { - "component_name": "IterationItem", - "params": { - "outputs": { - "index": { - "type": "integer" - }, - "item": { - "type": "unknown" - } - } - } - }, - "parent_id": "Iteration:PetiteBanksWarn", - "upstream": [] - }, - "Message:TenLizardsShake": { - "downstream": [], - "obj": { - "component_name": "Message", - "params": { - "content": [ - "\n\n\n\n{Agent:AfraidBearsShare@content}" - ] - } - }, - "parent_id": "Iteration:PetiteBanksWarn", - "upstream": [ - "Agent:AfraidBearsShare" - ] - }, - "begin": { - "downstream": [ - "Iteration:PetiteBanksWarn" - ], - "obj": { - "component_name": "Begin", - "params": { - "enablePrologue": true, - "inputs": { - "JD": { - "name": "Job Description", - "optional": false, - "options": [], - "type": "line" - } - }, - "mode": "conversational", - "prologue": "Hi there! I help you assess how well candidates match your job description. Just upload the JD and candidate resumes to begin." - } - }, - "upstream": [] - } - }, - "globals": { - "sys.conversation_turns": 0, - "sys.files": [], - "sys.query": "", - "sys.user_id": "" - }, - "graph": { - "edges": [ - { - "data": { - "isHovered": false - }, - "id": "xy-edge__beginstart-Iteration:PetiteBanksWarnend", - "source": "begin", - "sourceHandle": "start", - "target": "Iteration:PetiteBanksWarn", - "targetHandle": "end" - }, - { - "data": { - "isHovered": false - }, - "id": "xy-edge__IterationItem:EagerGiftsOpenstart-Agent:AfraidBearsShareend", - "source": "IterationItem:EagerGiftsOpen", - "sourceHandle": "start", - "target": "Agent:AfraidBearsShare", - "targetHandle": "end" - }, - { - "data": { - "isHovered": false - }, - "id": "xy-edge__Agent:AfraidBearsSharestart-Message:TenLizardsShakeend", - "source": "Agent:AfraidBearsShare", - "sourceHandle": "start", - "target": "Message:TenLizardsShake", - "targetHandle": "end" - } - ], - "nodes": [ - { - "data": { - "form": { - "enablePrologue": true, - "inputs": { - "JD": { - "name": "Job Description", - "optional": false, - "options": [], - "type": "line" - } - }, - "mode": "conversational", - "prologue": "Hi there! I help you assess how well candidates match your job description. Just upload the JD and candidate resumes to begin." - }, - "label": "Begin", - "name": "begin" - }, - "id": "begin", - "measured": { - "height": 76, - "width": 200 - }, - "position": { - "x": 50, - "y": 200 - }, - "selected": false, - "sourcePosition": "left", - "targetPosition": "right", - "type": "beginNode" - }, - { - "data": { - "form": { - "items_ref": "sys.files", - "outputs": { - "evaluation": { - "ref": "Agent:AfraidBearsShare@content", - "type": "Array" - } - } - }, - "label": "Iteration", - "name": "Iteration" - }, - "dragging": false, - "height": 300, - "id": "Iteration:PetiteBanksWarn", - "measured": { - "height": 300, - "width": 762 - }, - "position": { - "x": 664.2911321008794, - "y": 300.8643508010756 - }, - "resizing": false, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "group", - "width": 762 - }, - { - "data": { - "form": { - "outputs": { - "index": { - "type": "integer" - }, - "item": { - "type": "unknown" - } - } - }, - "label": "IterationItem", - "name": "IterationItem" - }, - "dragging": false, - "extent": "parent", - "id": "IterationItem:EagerGiftsOpen", - "measured": { - "height": 40, - "width": 80 - }, - "parentId": "Iteration:PetiteBanksWarn", - "position": { - "x": 61.93019203023471, - "y": 108.67650329471616 - }, - "selected": false, - "type": "iterationStartNode" - }, - { - "data": { - "form": { - "delay_after_error": 1, - "description": "", - "exception_comment": "", - "exception_default_value": "", - "exception_goto": [], - "exception_method": null, - "frequencyPenaltyEnabled": false, - "frequency_penalty": 0.5, - "llm_id": "", - "maxTokensEnabled": false, - "max_retries": 1, - "max_rounds": 1, - "max_tokens": 4096, - "mcp": [], - "message_history_window_size": 12, - "outputs": { - "content": { - "type": "string", - "value": "" - } - }, - "parameter": "Precise", - "presencePenaltyEnabled": false, - "presence_penalty": 0.5, - "prompts": [ - { - "content": "HR is asking about: {sys.query}\n\nJob description is {begin@JD}\n\nResume is {IterationItem:EagerGiftsOpen@item}", - "role": "user" - } - ], - "sys_prompt": "# HR Resume Batch Processing Agent \n\n## Mission Statement\n\nYou are a professional HR resume processing agent designed to handle large-scale resume screening . Your primary goal is to extract standardized candidate information and provide efficient JD matching analysis in a clear, hierarchical text format.And always use Chinese to answer questions, and always separate each resume information with paragraphs.\n\n## Core Capabilities\n\n### 1. Standardized Information Extraction\n\n- Extract 6 key data points from each resume\n\n\n- Normalize all information to consistent format\n\n- Ensure data quality and completeness\n\n- Provide confidence levels for extracted information\n\n### 3. JD Matching Analysis\n\n1. Score: [X/10] \n\n2. Matching Analysis: \n\n- Clearly state the main points of alignment between resume and job description. \n\n- Mention any strong matches in experience, skills, or education. \n\n- Indicate if there are any gaps or mismatches. \n\n\n\n- Content length must always be between 30-50 characters\n\n### Output Specifications\n\n\n\n\n**Important requirement**: No subheadings\n\n\n\n- Full name without titles\n\n- Primary phone/email in standard format\n\n- Most recent educational institution\n\n- Numeric value (years of experience or graduation year)\n\n- Current residence city only\n\n- JD Matching Analysis\n\n\n## Processing Workflow\n\n### Step 1: File Analysis\n\n### Step 2: Information Extraction\n\n### Step 3: JD Matching Analysis\n\n### Step 4: Text Formatting\n\n### Step 5: Output complete context\uff08Strictly keep one line per message and do not merge. The content of the second resume and the previous resume are not allowed to be on the same line\uff09", - "temperature": "0.1", - "temperatureEnabled": true, - "tools": [], - "topPEnabled": false, - "top_p": 0.75, - "user_prompt": "", - "visual_files_var": "" - }, - "label": "Agent", - "name": "Evaluation Agent" - }, - "dragging": false, - "extent": "parent", - "id": "Agent:AfraidBearsShare", - "measured": { - "height": 84, - "width": 200 - }, - "parentId": "Iteration:PetiteBanksWarn", - "position": { - "x": 294.68729149618423, - "y": 129.28319861966708 - }, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "agentNode" - }, - { - "data": { - "form": { - "content": [ - "\n\n\n\n{Agent:AfraidBearsShare@content}" - ] - }, - "label": "Message", - "name": "Evaluation Result" - }, - "dragging": false, - "extent": "parent", - "id": "Message:TenLizardsShake", - "measured": { - "height": 56, - "width": 200 - }, - "parentId": "Iteration:PetiteBanksWarn", - "position": { - "x": 612.0402980856167, - "y": 82.64699341056763 - }, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "messageNode" - }, - { - "data": { - "form": { - "text": "The agent can also save evaluation results to your Google Sheet using MCP.\n\nhttps://github.com/xing5/mcp-google-sheets" - }, - "label": "Note", - "name": "Google Sheet MCP" - }, - "dragHandle": ".note-drag-handle", - "dragging": false, - "height": 130, - "id": "Note:SixtyHeadsShout", - "measured": { - "height": 130, - "width": 337 - }, - "position": { - "x": 619.4967244976884, - "y": 619.3395083567394 - }, - "resizing": false, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "noteNode", - "width": 337 - }, - { - "data": { - "form": { - "text": "HR uploads a job description first, then submits multiple resumes via the chat window for evaluation." - }, - "label": "Note", - "name": "Candidate Evaluation Workflow" - }, - "dragHandle": ".note-drag-handle", - "dragging": false, - "height": 157, - "id": "Note:LuckyDeerSearch", - "measured": { - "height": 157, - "width": 452 - }, - "position": { - "x": 457.08115218140847, - "y": -6.323496705283823 - }, - "resizing": false, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "noteNode", - "width": 452 - } - ] - }, - "history": [], - "messages": [], - "path": [], - "retrieval": [] - }, - "avatar": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAABUYSURBVHgBtVoJmF1leX7Pueeeu29zZ5/MlkkmGZLMRBKCEIiAgAhCWFS0qAVp2ioicaOt2qpFBSs0glUjorilPjyhNbFAIQWCNIEhCUkkk2Qymczcmcw+c/f9nq3vf4a0aIEmFE+eeWY7c863vt/7vX8k/MH17aPZi2QH1sM0r7UgteE0LkmSICs6PnP2eVh6y10YP7gDWjaJ6LK1mD3xCmo6V2NBcxemZ4bhVv3QdB0OyeIrTFimBX4BvZLhJxkeXwBTw6/wqXy7ocMoZaHlcwcl0zzojtR+LfXc5thr3y2f+mLTsBW+byC7SZKsnXzoxtM1Xlyqx40XH38aKM2gXCmhEKMBFo2DAkmWEAzXwDQMqLIb0qsvVZwuaFoZslOGoZegVUy4XG7k8zm4PAFohSwMrQDZ44elqislWb7Z29I1XH3VnZvCF90c/j0HhPFGOWcbjrdwqR4ZsQO/A5xeWOUyzGIWDncQtAxOTwjeQBWKhTQsixE1KpBlmf4xC04nTN5fKdFo1cmsMJswYFTKMNJTvEeGk89EIc8MOyDrJrRycWPFVHaGL9oY/m8HTC33FYZsJd7KxZcqjHIqEefTFDhoCGQVRnYKTkWCGqqBKjmgmwZ0ZkGSaahDQVmrwGkAerkEB+93qioqLC2LDhbSk5CLef7MBb1YgNPv53scgFZkEJhJp2ulKWtfsR3YdLTY9lYj/1ovikURJRUqy0Dih0wDDThRXd3CHpFRoSHiPpn9YloaZMNEqZyz+8fDMtEZXYMOlPMpyKlZ3mlBt9gnNFr8nYPPLhfTzIIFg+XJ0tzov+Dmi+RTnrzliz0IBscolSE5VciWA0q4FkYhA2+4Gq5AEGUayoJm3SssHwZSN2DpFUae9yoKs0NnNQMmKigmJtgPdNahinJhGWnQSxXbcUuUkJahO5IdFEsrXytblnQGpSPZFluvficARHxn/5QPh8vHqOtQvCGWC3uBhrtZGvlswr7PqThZ3zScX5uMoniQrunIxyfhdDlRyaZhpKbpmAyT/VEbqYKD/cE+Z4boJIMg+sKyxLPcwpz18pnVvoGCwYezDi3CYMjJ1IpI2Eho8WtpPqp0RHb5UYyP29HWKhpUloBhzpeJJXphPhYo5mZt5xSWUTnNPnJ6YLlZ82x+ndnwFSZRyszSaZPPG7X/TnE4BXbzOWabcrqm21GnsVc1qDAl1jr7P6RY2JvSENfpAB9amRmCSIRMJ+CrRoWR1dtMG0JlNx2gpzIjb4rnmfM9YNEh2elmUCooVYpwuelApgiJKFWOLEBJlxBgiaUnh+FrOcuOvoOZtMRTxPNO1wFRdyrvdjtkOCw7AMwHG42GCPizmHZZL2J011ZmwEWjVJSI6U6F97P22b6s3wpLgQ0sXkwDFdsVcE5UIZechaKVYBFCBXopwRCkkgaD88Ki096ms2Da5cqXEfUEGkmmmDSnfVl2dPcmKshqbCbRcoRBggkag+LXDvadB5XYXqhVDRxGPjZvEZWKzjtlGi7BZbHR1flX5jmpnYRdTS+z0asxMzFAgypws/Y1iSXo9EMh9PpDYU71OJ2PQxGwyuZ2CSdovCXK6fQdkGz4qvMAzT4FHpZJVLWwP61jaKKERJLDi7+X+dTC5DFEWrpQnjmJBmcFoY5aIDeHI+MWyyTPxi3bfWAS252s9wozU4nPwktEkvlR8VTBK1vQFIE8jE1mCqjrgMnsOTjQbGtYBWIQnnYJiUZzsweaWct+loVLBIE/C3hVDA7E4fV6WTpuYroXanIaqeQc6qK1yMwO4MToFJ7ZcxirljSjXCrZhpgsIfHUQKQW6ZmYGKYIBTzIML5OUSgu1S4YtaEetbXVMEvzE9wuIXGxjBTVeyY9QPzmx8mChrG0iYGMhpdmNEwWTBzqH4WqulEqsGQ4WctGAZXBo2ir8WDPzucQVAwsXtSJJ/f0Y21PG1LxBJ0o29Do90YwO3wIixfUYyStkXZE4ZZ1mytJXn7Ek3wmhxlrXjSeScMdDgf7kTTD7zl9B15NA/ZWLAyw8MZUCVrIjZRZRrngQEd7FK1tbaz7so0OrU1B9B0bwYrlS2DQe53N4lIdiM+maTQjyVKT3V6k80ksCLowFc8yG/WIT4+jqylCGFZY3zSvVMT4bNymKAI4VJauKhnIL1zF+6Nn4oDEOWliRURlZBTUc440qXRkrog1nW0YnuaQ4ZCKhIPQc3nU19QgMXAEwdparGBd+xPkN6z7w7EpLGmrR5lN7Ys0ID7Sj/rqEHIloLOzGR9mUEJ19bCSaeSGRzhDsuRaJHIMlMwsiOibkgvvvbgR3obmM+sBBh1HKjLcRKGEZiFGBJoYLkJlqtOFBEamUmhuqCX/iWBoaBh1i5tgziWwkhlZXhdFe8ciFDnUzqGhLAZkSgbeuagK+w8MwOOmUet6ECKc+iMRuAjJChvNzGfJSJkNDkSV01dQisiCKtxy/TrUtLacWQ9wHKEmkUMgV0FNwUJzQcbsoTH8+HtfQotEjkLkmaqoaG9fyEXmCPx+Hz4a8MHNkovUNqI+6oNE7r+zpOOCnsW4qKsaL7ywH+suOBs3vf8KKGzUJ/iiCIEmLjWgKhTh3DFJEANweAOwqlvJUBUsvu4SXLPyTzGyf/+Z9YCTkbyw0Y+VLKN3tPsws+0p5B/5Iqz/fBjXXLAC1ol9iM/MYe/RGTSuuhA/+uLt+OyOx9GLKnQsW4zVTW5YeULohvXo3TeB3zz1NG79wGU4l33iIZpJxH2lugoPbdmOe+67CaF2Gkx0s/wkh3FypNkJrP7srXj2c3ejimVY4bOUNysaAZSC69ij36YApAtsRoMjeWh/DLvu/TK02nbIXe8jjIbQuXwpBl7pY73mMPfxbUikspyms/j13BxcmXr4irNgJSOz8TtoXtWJQ49twV/euBGP7TqIR3oHIWhtUlPQ5Cxhw5KF2DQ3jMXveQ+cC6rZ8GFcccMa/FXHlQh2tKBA4qeQwb6uA9IpqOW/SeJ1E2/TCVsGv35i0kBVXQB3Xvt+tFy+gQNsFuaT34cmX4hPfPozuPPv/okDjwOm2YO8QcwvzCJzaBdmlrfj/Bof3nXuEuzYfQwTd1yJG9PLiEwK+Y5OGA7CjRzSB3pxzo8fgH/5elRHA9g/sJU03cfmdSL22+fhDpAkkqKU6az3jeaAIEzzdS/h060hZATkifhwcr6v3ovVJHGbPvtdbGzxYtHIHoRuvwu7+2dx90+24YFv34HQ+NNYGp/DinAAvb98CK3FQXzymnNx24cux5N//1Fsv/UcXPjEI3j2h/eQbnPyyn46ncdA72/R/SdX4+TjL6F+6TIY4TrSiDk4CZdaKY3JQ/2A343M8RizRFQsFt+8BwRBC/LGelWy+YwsUT3gP7u4bELFzPhr0FxXh/GiB0lPC8y2d+KTH9mMb5y/ADon9tBcEnevW4zw0V4Uy8Rv2YtwWMX1wSIjb2LrM3uwfXc/Bg/uQY6LfOMlV9HQPuSyGSoV5FucA7nUHEokei5vLZLHjuOxrf+I4z//OnLnd7+ZAwRONtU3j+RxZZPH3mmFE7tTJh5/aRy3/+Bb+Oa+Axit74L7SB+kUBRN7V0wTw6g9sPn4V3dK9HAKLrcTD2H2w++/AX8+/c2w+uh47PjaIn4iU4OTKQtZJJjkJMJXH3PPXjhvl8htKCJiEEGy4FlcrJr+TSc4XoGREX3ii5c9a418J5/HZ5Pxt/IARFdyY50ndvCw8T6j7b4uGtIaKzzYsvMMXz93x6CdcM6dLx7LUqhLqxZ3oL0/qdw3YUrsC7bhwdvei9C4RCjFkZ9IIwBPuwH3/8W8uMZhJddjPpgBPuefhYxUoUlUTeu+tgN+IvuTnzsvT2YyGh2QxvMgkVaTm/42Q0He2WUAel8MobyRAyj6SQcl3/qb776Rk6IZrZXXrH9kB3eyPGbJRE7r7GdZSP6n5KJvx5nR3z4h6VZqmHjyJEe/OaRf4bO0mtsW4bsdAzx2DEkUyWUSC2bw1GsuvwSlIaOIU/ovKzGi6s/+H58/OLVcA7FcMXyhfhEq4IHXjxC8lWy9SFukZzcLi5JQeSO7ceSlmb0tLYjU0vB4N7+jIU3uSRJFI6QRUhv6VSIKHDf5+8iIcvi5w/+LT7/zX/Fp97djGDvVhR9EXTw4WMz40jOTaOhponLfQp9/cfhdLhxJFmAj85f29VM38PoXnU2dpEA3tTdjZ3bt9mol5hkOQ3244FsFV48Pg4jMY665lZs+LNbUCD9+M2//Aqj+/Zg2bpLMRhPvVkGXusFia3YopgJfwDYygn453d8Dj95cTemtSHM7XwZt127FuM05sBLe9C9/CyMjY0jk05hQUMrXNGzoJLLKJEwp+4qlDhYmjuXUjMK4rzOTsylM6hQfUiwZCYmRrG74MResw7R/BRc3ACzsgsDcQ2+VApVuRl0k289c7if2hJOx4H5NhFLvEwiZXB3ba1bjoNH96CW2Tix+xDGXngaabb4IjmP42Mn8fyuA7js0rUYmUliYc0C1DS1IlTjR3PPWbj0qmtsRaKhrQVhSi6B6ijK2SxfQ6OnSxgJdGDc14YPNARwcZ0Lq5mpXbMchOFmOLmxyUSkNLe5UfKonrN7TmcjOzUTKIaQUOlEB3MuizmyTyNbQH7HL9B5yQ34XdOl+GBjDENPPg+9UMHBvgEcJzoNHDmC6y69Gk88vQNXbrgZt69fj+VrzkFTcxuqG+sQJseZKpSx5annMHj8BMZQg1TJwnbuww6up7d8+lbc2NSI2o7VuPtHv4SX62U+laDDLntdPaOdWHR1gLJH397H0N26Avv+Ywu3JBUnDx/DXT+8GxN7B3DF6l4cODmDwcFRROsXwuPwYvOvfoG//tJG/OzhX+CC895JCUWsgmSjMzN4Jj2HvvEJZEcnkfU0oV42cceGa7nEGNjxaBkPfuv7CK1eC+lQDlV+7sslyjacT5qNlOaZODB/pbM5fPC2q/Ho5meQzc9BiTai/dxL8OhDv8WathCG5GZctmoRDr2yD2NDh9kXGj520Trc+Mk7UReh1MLx7/MGEZvrxVgiC7VlERyk5unZBAoB2R6M3/juFgpbQXiH+yFxzfSQZxEFOJVNW90Q1Fjs1ApFszN2QNALw3Tggiu6MXbsAGaSz8IXbsCJJ7bg5aGjaFl5Lrb9+GGWx0JU13dg/eaf4SffuxdmoAkjnKz9O3sZOQpdpBB+ro/+euqq3J+zM5Pw1XRAnxpGpJTCjFrFPYC8OlewkVCn6CtUILFLSzZeUXm1cIYr5atXhTpoQ0cTPvS5m2HMTaJj5VoUxcNCVcgkkmjqeTdV5yKmJ05g8wfegZGdv4RBVUJnxmSHrS4LoZHMlqr2WIzlQnHLW43c9EmUE1OkHBxgSUIo5UlL8djSi8klyhTCF8Ve02bGMrxcO18XhQSZc4udlKugQhhTHJKtV4oP2SE4EWyNc0F7NY4Pz2Hs6CEoPNgoFXNcAw+h7tzrodHgwvSgrbDp4rCDjFLETWEPSZRDHPzeFAyXarSe5T5JmiBlEpCYTUMNwcHnmZZgwAZc3AcqgiGLBma2LIv3Ur6MnRh+/UHmJtLE+oWuSaWMxkuGhXk1RraPhfCqoiYOK0LRunm1jbl0cetS3Bx6hRIb1W1LH0I8obyDQnleVROzRCiPDvtcYf5Jkjyv8xhi4pv/Y4cmZEprXoYUQS1yzvQsCaL3xSGYaoACQun1e0Dwc4+XKgKPhGR5vvLn0dTCKY3/1I9kSRNKOKXAMKbv/RRmZnNU7jSWASUAqskFZiCdr+AaYrZuFpHhOUI8VUaC+lCcgy7H3UFntEVfeFxeW9EQKOPizKmqipIMeqklpVHr8aC6pQ0/HeeApCM1nCFO6Q1QSBy8hYm9v2f4KTkZrzr0B5fBpXzVldfDu2MLprIakqYHszmTy34RBTWHpQ1udDQuwVAsBqOBGil50TSpdpYKxnC8glkizGGWn3i3cNxu4GCU0KngyhXLeVC42Ba9Or01mOOcEUJXKBD6v7nQaV+UPAyvf54ynBiE9eh3wBnLfTaMPEf+0MSMvZBXMVNGLgkvxVvV52bpe5CYTjFrJZaQiVhszD6GEnAtkt+5oBaXrzkbM84oJqdG4BX9Q9VO6KT2IeLb5sApP2zFlbrpQg/u+8idWJwbwtVn1aM66Mf4yQlMUUOlqI5IwMvoUg9iBnKcFUHivcKTDCGMeV0ue/LnijyxKRS4zOig9kWhN0QnKW1Gq+EhzbaUP4ID805Y3AMU3HHOOrjqqPEf6UVN1IP3XHIFWh1zaFC5mbEXdMVrG2kPJNa81+e3P7sZ3RIblChhP0+hsSOxEUTrauio3z6LS+eYVlJ2ARAxetCGt/FyMYJ9ew7a2VB4mOBuWYwsYWf7pEYkNBDmLOiqr0W0PE3BK4C2EDezomJDtEl4LlP5EGBXIbqJv1cMBW0trcjz7Cw2M0u41tBGUSs5M3mQ67m1nU15B96mS5w6Oj0K+ve8DDkQoJResQ/9nJE65CZPIEpKrekOHDeDeLGvD48dimPlykW4bQnlQ54fpHi2rJULHHYOCJCczpR4jlDgDBLyvlDodAS4oxcoVYrTe8Wy5G08nX/bHBCXOCcbPNoPhRuUJI6MqFxX2LgQE9UZmj+BEYOrphZVTYtxLDOHDVsHCNucyFS2XSwNX1U12omqi2t9qKUKLfF4tSpAJ8mRTJ6sFKhIZMvlrylf6Ao8d29/+v63MwvZDEWn6k5EPLWU0cXJe4HIk7IP67yhWrFcwJANBOraOJXdMMaPU7gSZ8mMOZFKp3yTg4/LTRq7hkYR4kF3a5WKeu7kXj1no1VFM+5/an9fzAb0TcPJsFGWd9KJt3Za/5pLZqqLPCN+8Kd7bMgrlfKwuFZWDIHdLAUONpWHdIJ2CMfiAweofTqZFReqkUSMArFTLE6+KBuUjlIcg7iXTW+ShgiF2izlD1LKuBhIpew5+5n2SMrhMvkD6378Py/B0XP5ki37Cb5U5sDSGGXJTXocrGOk/SgJesLxnZgYQaCFq2VTNyqzI1wzl8FPABASjsLJa5HvSOK/GFCVk90BjjEGyCjfH17YYxsv3ve/Ruqmo8k2qtBf5W963kpGHDRu6MQoHvv1YQSDFLuYAY2NKZEqKCQ9PL/kgUgOpdQkCiRyVe09mNz9CCzOA53nxLKARkoxclUjZyMPCHkAwjTELL24nZ5t06cOPvfa9/0XJgXg8a7ODMwAAAAASUVORK5CYII=" -} diff --git a/internal/agent/dsl/testdata/resume.json b/internal/agent/dsl/testdata/resume.json deleted file mode 100644 index 768e30d858..0000000000 --- a/internal/agent/dsl/testdata/resume.json +++ /dev/null @@ -1,461 +0,0 @@ -{ - "globals": { - "sys.conversation_turns": 0, - "sys.date": "", - "sys.files": [], - "sys.history": [], - "sys.query": "", - "sys.user_id": "" - }, - "graph": { - "edges": [ - { - "data": { - "isHovered": false - }, - "id": "xy-edge__Filestart-Parser:HipSignsRhymeend", - "source": "File", - "sourceHandle": "start", - "target": "Parser:HipSignsRhyme", - "targetHandle": "end" - }, - { - "data": { - "isHovered": false - }, - "id": "xy-edge__Parser:HipSignsRhymestart-TitleChunker:FlatMiceFixend", - "source": "Parser:HipSignsRhyme", - "sourceHandle": "start", - "target": "TitleChunker:FlatMiceFix", - "targetHandle": "end" - }, - { - "data": { - "isHovered": false - }, - "id": "xy-edge__TitleChunker:FlatMiceFixstart-Extractor:ThreeDrinksActend", - "markerEnd": "logo", - "source": "TitleChunker:FlatMiceFix", - "sourceHandle": "start", - "target": "Extractor:ThreeDrinksAct", - "targetHandle": "end", - "type": "buttonEdge", - "zIndex": 1001 - }, - { - "data": { - "isHovered": false - }, - "id": "xy-edge__Extractor:ThreeDrinksActstart-Extractor:ItchyFoxesStriveend", - "source": "Extractor:ThreeDrinksAct", - "sourceHandle": "start", - "target": "Extractor:ItchyFoxesStrive", - "targetHandle": "end" - }, - { - "data": { - "isHovered": false - }, - "id": "xy-edge__Extractor:ItchyFoxesStrivestart-Extractor:BusyClocksRushend", - "source": "Extractor:ItchyFoxesStrive", - "sourceHandle": "start", - "target": "Extractor:BusyClocksRush", - "targetHandle": "end" - }, - { - "data": { - "isHovered": false - }, - "id": "xy-edge__Extractor:BusyClocksRushstart-Extractor:CuteSignsCutend", - "source": "Extractor:BusyClocksRush", - "sourceHandle": "start", - "target": "Extractor:CuteSignsCut", - "targetHandle": "end" - }, - { - "data": { - "isHovered": false - }, - "id": "xy-edge__Extractor:CuteSignsCutstart-Tokenizer:KindHandsWinend", - "markerEnd": "logo", - "source": "Extractor:CuteSignsCut", - "sourceHandle": "start", - "target": "Tokenizer:KindHandsWin", - "targetHandle": "end", - "type": "buttonEdge", - "zIndex": 1001 - } - ], - "nodes": [ - { - "data": { - "label": "File", - "name": "File" - }, - "dragging": false, - "id": "File", - "measured": { - "height": 49, - "width": 200 - }, - "position": { - "x": 239.52494800353588, - "y": 92.44515504032671 - }, - "selected": false, - "sourcePosition": "left", - "targetPosition": "right", - "type": "beginNode" - }, - { - "data": { - "form": { - "outputs": { - "html": { - "type": "string", - "value": "" - }, - "json": { - "type": "Array", - "value": [] - }, - "markdown": { - "type": "string", - "value": "" - }, - "text": { - "type": "string", - "value": "" - } - }, - "setups": [ - { - "fileFormat": "pdf", - "flatten_media_to_text": true, - "output_format": "json", - "parse_method": "DeepDOC" - }, - { - "fileFormat": "spreadsheet", - "flatten_media_to_text": true, - "output_format": "html", - "parse_method": "DeepDOC" - }, - { - "fileFormat": "image", - "output_format": "text", - "parse_method": "ocr" - }, - { - "fields": [ - "from", - "to", - "cc", - "bcc", - "date", - "subject", - "body", - "attachments" - ], - "fileFormat": "email", - "output_format": "text" - }, - { - "fileFormat": "markdown", - "flatten_media_to_text": true, - "output_format": "json" - }, - { - "fileFormat": "text&code", - "output_format": "json" - }, - { - "fileFormat": "html", - "output_format": "json" - }, - { - "fileFormat": "doc", - "output_format": "json", - "vlm": {} - }, - { - "fileFormat": "docx", - "flatten_media_to_text": true, - "output_format": "json" - }, - { - "fileFormat": "slides", - "output_format": "json", - "parse_method": "DeepDOC" - } - ] - }, - "label": "Parser", - "name": "Parser_0" - }, - "dragging": false, - "id": "Parser:HipSignsRhyme", - "measured": { - "height": 197, - "width": 200 - }, - "position": { - "x": 252.18327231534056, - "y": 157.25954787377458 - }, - "selected": true, - "sourcePosition": "right", - "targetPosition": "left", - "type": "parserNode" - }, - { - "data": { - "form": { - "fields": "text", - "filename_embd_weight": 0.1, - "outputs": {}, - "search_method": [ - "embedding", - "full_text" - ] - }, - "label": "Tokenizer", - "name": "Indexer_0" - }, - "dragging": false, - "id": "Tokenizer:KindHandsWin", - "measured": { - "height": 113, - "width": 200 - }, - "position": { - "x": 663.5327731044026, - "y": 544.5236681687471 - }, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "tokenizerNode" - }, - { - "data": { - "form": { - "hierarchy": "1", - "include_heading_content": false, - "method": "hierarchy", - "outputs": { - "chunks": { - "type": "Array", - "value": [] - } - }, - "promote_first_heading_to_root": false, - "root_chunk_as_heading": true, - "rules": [ - { - "levels": [ - { - "expression": "^\\s*(?i:(?:\\d+[\\.\\)]\\s*)?(?:EDUCATION|ACADEMIC\\s*BACKGROUND|ACADEMIC\\s*HISTORY|EDUCATIONAL\\s*BACKGROUND|RELEVANT\\s*COURSEWORK|COURSEWORK|EXPERIENCE|WORK\\s*EXPERIENCE|PROFESSIONAL\\s*EXPERIENCE|RELEVANT\\s*EXPERIENCE|EMPLOYMENT\\s*HISTORY|CAREER\\s*HISTORY|INTERNSHIP\\s*EXPERIENCE|PROJECTS|PROJECT\\s*EXPERIENCE|ACADEMIC\\s*PROJECTS|PROFESSIONAL\\s*PROJECTS|SKILLS|TECHNICAL\\s*SKILLS|CORE\\s*COMPETENCIES|COMPETENCIES|QUALIFICATIONS|SUMMARY\\s*OF\\s*QUALIFICATIONS|CERTIFICATIONS|LICENSES|CERTIFICATES|AWARDS|HONORS|HONOURS|ACHIEVEMENTS|PUBLICATIONS|RESEARCH|RESEARCH\\s*EXPERIENCE|LEADERSHIP|LEADERSHIP\\s*EXPERIENCE|ACTIVITIES|EXTRACURRICULAR\\s*ACTIVITIES|ACTIVITIES\\s*(?:&|AND)\\s*SKILLS|INVOLVEMENT|CAMPUS\\s*INVOLVEMENT|VOLUNTEER\\s*EXPERIENCE|VOLUNTEERING|COMMUNITY\\s*SERVICE|LANGUAGES|INTERESTS|HOBBIES|PROFILE|PROFESSIONAL\\s*PROFILE|SUMMARY|PROFESSIONAL\\s*SUMMARY|CAREER\\s*SUMMARY|OBJECTIVE|CAREER\\s*OBJECTIVE|PERSONAL\\s*INFORMATION|CONTACT\\s*INFORMATION|ADDITIONAL\\s*INFORMATION|TRAINING))\\s*[::]?\\s*$" - } - ] - }, - { - "levels": [ - { - "expression": "^\\s*(?:\\d+[\\.、\\)]\\s*)?(?:教育背景|教育经历|学历背景|学术背景|技术背景|工作经历|工作经验|实习经历|项目经历|项目经验|科研经历|研究经历|校园经历|实践经历|专业经历|职业经历|技能|专业技能|技能特长|核心技能|技术栈|个人技能|工作技能|职业技能|技能与评价|技能与自我评价|工作技能与自我评价|职业技能与自我评价|证书|资格证书|职业资格|资质证书|获奖情况|获奖经历|荣誉|荣誉奖项|奖项|科研成果|论文发表|发表论文|领导经历|学生工作|校园活动|社团经历|活动经历|志愿经历|志愿服务|社会实践|语言能力|语言|自我评价|个人评价|自我总结|个人总结|个人优势|个人简介|个人信息|基本信息|联系方式|求职意向|应聘意向|职业目标|求职目标|兴趣爱好|兴趣特长|培训经历|其他信息|附加信息)\\s*[::]?\\s*$" - } - ] - } - ] - }, - "label": "TitleChunker", - "name": "Title Chunker_0" - }, - "dragging": false, - "id": "TitleChunker:FlatMiceFix", - "measured": { - "height": 73, - "width": 200 - }, - "position": { - "x": 524.2908769627791, - "y": 53.05515313482098 - }, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "chunkerNode" - }, - { - "data": { - "form": { - "field_name": "metadata", - "frequencyPenaltyEnabled": true, - "frequency_penalty": 0.7, - "llm_id": "MiniMax-M2.7@MiniMax", - "maxTokensEnabled": false, - "max_tokens": 256, - "outputs": { - "chunks": { - "type": "Array", - "value": [] - } - }, - "presencePenaltyEnabled": true, - "presence_penalty": 0.4, - "prompts": "Content: {TitleChunker:FlatMiceFix@chunks}", - "sys_prompt": "Act as a precise resume metadata extractor. Extract stable, chunk-supported metadata from the provided resume content.\n\nRules:\n1. Use only information explicitly stated in the content. Do not infer, guess, normalize, or add missing facts.\n2. The input may be only one chunk of a resume. Extract only what this content directly supports.\n3. Use only these field names:\ncandidate_name, gender, phone, email, city, location, nationality, linkedin, github, website, highest_degree, degree_levels, school_names, majors, graduation_years, work_experience_years, current_job_title, job_titles, company_names, job_experience, industries, target_job_titles, target_locations, employment_types, skills, certificates, awards, summary_tags\n4. Ignore detailed responsibilities, project descriptions, achievement narratives, self-evaluation, and other low-value local details.\n5. Keep values in the same language as the source text whenever possible.\n6. Remove duplicates and keep only concise, high-value metadata.\n7. Return only fields that are explicitly supported by the content. Do not return empty or unsupported fields.\n\nField guidance:\n- highest_degree: highest explicit degree level mentioned\n- degree_levels: all explicit degree levels mentioned\n- school_names: explicit school, college, or university names\n- majors: explicit fields of study\n- graduation_years: explicit graduation years only\n- work_experience_years: only if explicitly stated\n- current_job_title: only if explicitly current or most recent\n- job_titles: explicit role titles\n- company_names: explicit employer names\n- job_experience: concise structured work entries explicitly supported by the content, preferably including title, company, and time information when available\n- industries: explicit industry names only\n- target_job_titles: explicit desired roles only\n- target_locations: explicit desired work locations only\n- skills: concise, core, search-useful skills explicitly mentioned\n- certificates: explicit certificate names only\n- awards: explicit award names only\n- summary_tags: short, high-value tags strictly supported by the content\n\nReturn only the extracted metadata. Do not output explanatory text.", - "temperature": 0.1, - "temperatureEnabled": true, - "tenant_llm_id": 29, - "topPEnabled": true, - "top_p": 0.3 - }, - "label": "Extractor", - "name": "Auto Metadata" - }, - "dragging": false, - "id": "Extractor:ThreeDrinksAct", - "measured": { - "height": 89, - "width": 200 - }, - "position": { - "x": 550.8123774842874, - "y": 161.4998493859579 - }, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "contextNode" - }, - { - "data": { - "form": { - "field_name": "keywords", - "frequencyPenaltyEnabled": true, - "frequency_penalty": 0.7, - "llm_id": "MiniMax-M2.7@MiniMax", - "maxTokensEnabled": false, - "max_tokens": 256, - "outputs": { - "chunks": { - "type": "Array", - "value": [] - } - }, - "presencePenaltyEnabled": true, - "presence_penalty": 0.4, - "prompts": "文本内容\n[在此处插入文本]", - "sys_prompt": "角色\n你是一名文本分析员。\n\n任务\n从给定的文本内容中提取最重要的关键词/短语。\n\n要求\n- 总结文本内容,并给出最重要的5个关键词/短语。\n- 关键词必须与给定的文本内容使用相同的语言。\n- 关键词之间用英文逗号分隔。\n- 仅输出关键词。", - "temperature": 0.1, - "temperatureEnabled": true, - "topPEnabled": true, - "top_p": 0.3 - }, - "label": "Extractor", - "name": "提取器_0" - }, - "dragging": false, - "id": "Extractor:ItchyFoxesStrive", - "measured": { - "height": 89, - "width": 200 - }, - "position": { - "x": 558.7638130889704, - "y": 260.698149098317 - }, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "contextNode" - }, - { - "data": { - "form": { - "field_name": "questions", - "frequencyPenaltyEnabled": true, - "frequency_penalty": 0.7, - "llm_id": "MiniMax-M2.7@MiniMax", - "maxTokensEnabled": false, - "max_tokens": 256, - "outputs": { - "chunks": { - "type": "Array", - "value": [] - } - }, - "presencePenaltyEnabled": true, - "presence_penalty": 0.4, - "prompts": "文本内容\n[在此处插入文本]", - "sys_prompt": "角色\n你是一名文本分析员。\n\n任务\n针对给定的文本内容提出3个问题。\n\n要求\n- 理解并总结文本内容,并提出最重要的3个问题。\n- 问题的含义不应重叠。\n- 问题应尽可能涵盖文本的主要内容。\n- 问题必须与给定的文本内容使用相同的语言。\n- 每行一个问题。\n- 仅输出问题。", - "temperature": 0.1, - "temperatureEnabled": true, - "topPEnabled": true, - "top_p": 0.3 - }, - "label": "Extractor", - "name": "提取器_1" - }, - "dragging": false, - "id": "Extractor:BusyClocksRush", - "measured": { - "height": 89, - "width": 200 - }, - "position": { - "x": 592.4119346324834, - "y": 353.6113302354397 - }, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "contextNode" - }, - { - "data": { - "form": { - "field_name": "summary", - "frequencyPenaltyEnabled": true, - "frequency_penalty": 0.7, - "llm_id": "MiniMax-M2.7@MiniMax", - "maxTokensEnabled": false, - "max_tokens": 256, - "outputs": { - "chunks": { - "type": "Array", - "value": [] - } - }, - "presencePenaltyEnabled": true, - "presence_penalty": 0.4, - "prompts": "要总结的文本:\n[在此处插入文本]", - "sys_prompt": "扮演一个精准的摘要者。你的任务是为提供的内容创建一个简洁且忠实于原文的摘要。\n\n关键说明:\n1. 准确性:摘要必须严格基于所提供的信息。请勿引入任何未明确说明的新事实、结论或解释。\n2. 语言:摘要必须使用与原文相同的语言。\n3. 客观性:不带偏见地呈现要点,保留内容的原始意图和语气。请勿进行编辑。\n4. 简洁性:专注于最重要的思想,省略细节和多余的内容。", - "temperature": 0.1, - "temperatureEnabled": true, - "topPEnabled": true, - "top_p": 0.3 - }, - "label": "Extractor", - "name": "提取器_2" - }, - "dragging": false, - "id": "Extractor:CuteSignsCut", - "measured": { - "height": 89, - "width": 200 - }, - "position": { - "x": 616.822830981782, - "y": 449.2756537664745 - }, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "contextNode" - } - ] - }, - "variables": [] -} diff --git a/internal/ingestion/component/extractor.go b/internal/ingestion/component/extractor.go index 3c8d38b5e3..64679dc1bd 100644 --- a/internal/ingestion/component/extractor.go +++ b/internal/ingestion/component/extractor.go @@ -1540,8 +1540,7 @@ func fitExtractorMessages(ctx context.Context, db *gorm.DB, llmID string, msgs [ // when chunks is non-empty. The python rag/flow/extractor/extractor.py // build_existing_prompt path performs the same substitution at // runtime; the Go port surfaces it as a regex on the prompt -// template so the resume template's `{TitleChunker:FlatMiceFix@chunks}` -// reference resolves without invoking a template engine. +// template so the reference resolves without invoking a template engine. // // Substitution is opt-in: when chunks is nil/empty the placeholder // is left intact so a misconfigured template surfaces as a @@ -1582,9 +1581,7 @@ func buildExtractorMessages(system, prompt, chunkText string, chunks []map[strin // {CmpName:ParamName@chunks} // // The CmpName and ParamName are both matched but ignored — the -// substitute is always "the joined chunk text" today, because the -// only @chunks reference in production templates is the resume -// template's `{TitleChunker:FlatMiceFix@chunks}` pattern. The +// substitute is always "the joined chunk text" today. The // CmpName/ParamName parsing exists so a future per-component // substitution can extend the function without breaking the // existing call sites. diff --git a/internal/ingestion/component/extractor_test.go b/internal/ingestion/component/extractor_test.go index 678079f307..e83688f5f8 100644 --- a/internal/ingestion/component/extractor_test.go +++ b/internal/ingestion/component/extractor_test.go @@ -1722,51 +1722,6 @@ func TestExtractorComponent_Invoke_AppendsChunkTextWhenNoPlaceholder(t *testing. } } -// TestExtractorComponent_Invoke_ResumeTemplateChunksPath verifies the resume -// template path: a prompt referencing {@chunks} (e.g. {TitleChunker:FlatMiceFix@chunks}) -// must still deliver the chunk body to the LLM. {@chunks} is resolved by -// substitutePromptPlaceholders in buildExtractorMessages, NOT by the simple -// placeholder loop — so contentPlaceholders does not see it, suppression must -// not trigger, and the chunk text arrives via the automatic append. Regression -// guard: if someone adds {@chunks} to contentPlaceholders or the suppression -// logic starts matching regex-style placeholders, the resume path silently -// breaks (chunk body vanishes from the LLM call). -func TestExtractorComponent_Invoke_ResumeTemplateChunksPath(t *testing.T) { - stub := withStubChatInvoker(t, - stubResponse{Content: "answer"}, - ) - - c := &ExtractorComponent{Param: schema.ExtractorParam{ - FieldName: "out", - Prompt: "Resume: {TitleChunker:FlatMiceFix@chunks}", - LLMID: "gpt-4o-mini", - }} - _, err := c.Invoke(t.Context(), nil, map[string]any{ - "chunks": []map[string]any{{"text": "resume chunk body"}}, - }) - if err != nil { - t.Fatalf("Invoke: %v", err) - } - - stub.mu.Lock() - defer stub.mu.Unlock() - var userContent string - for _, msg := range stub.lastReq.Messages { - if msg.Role == eschema.User { - userContent = msg.Content - } - } - // {@chunks} must be resolved by substitutePromptPlaceholders. - if strings.Contains(userContent, "{TitleChunker:FlatMiceFix@chunks}") { - t.Errorf("prompt still contains literal @chunks placeholder: %q", userContent) - } - // Chunk body must arrive in the final message (via the append path, - // since {@chunks} does not suppress the automatic chunk-text append). - if !strings.Contains(userContent, "resume chunk body") { - t.Errorf("chunk body missing from LLM call — resume path broken: %q", userContent) - } -} - // TestExtractorComponent_Invoke_SystemPromptPlaceholderSuppressesAppend // verifies that a content-bearing placeholder in systemPrompt (not just // prompt) also suppresses the automatic chunk-text append. The chunk body diff --git a/internal/ingestion/component/slice3_test.go b/internal/ingestion/component/slice3_test.go index c89ddfd0bc..387e1118d7 100644 --- a/internal/ingestion/component/slice3_test.go +++ b/internal/ingestion/component/slice3_test.go @@ -26,7 +26,7 @@ import ( ) // TestSubstitutePromptPlaceholders_ReplacesAtChunks pins the -// resume-template pattern `{TitleChunker:FlatMiceFix@chunks}`. +// `{ComponentName:ParamName@chunks}` placeholder substitution. // The substitute is the joined chunk text. func TestSubstitutePromptPlaceholders_ReplacesAtChunks(t *testing.T) { prompt := "Extract metadata from: {TitleChunker:FlatMiceFix@chunks}"