Major Changes: - Add StreamingService with OpenAI Compatible format - Upgrade Chat component V2 with Ant Design X integration - Implement AIA module with 12 intelligent agents - Update API routes to unified /api/v1 prefix - Update system documentation Backend (~1300 lines): - common/streaming: OpenAI Compatible adapter - modules/aia: 12 agents, conversation service, streaming integration - Update route versions (RVW, PKB to v1) Frontend (~3500 lines): - modules/aia: AgentHub + ChatWorkspace (100% prototype restoration) - shared/Chat: AIStreamChat, ThinkingBlock, useAIStream Hook - Update API endpoints to v1 Documentation: - AIA module status guide - Universal capabilities catalog - System overview updates - All module documentation sync Tested: Stream response verified, authentication working Status: AIA V2.0 core completed (85%)
83 KiB
IIT Manager Agent - Phase 1.5 AI撖寡<E69296><E5AFA1>賢<EFBFBD>撘<EFBFBD><E69298>𤏸恣<F0A48FB8>?
<EFBFBD><EFBFBD>𧋦: v3.0嚗<EFBFBD><EFBFBD>蝞<EFBFBD><EFBFBD>?+ 銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹?+ Dify<66>亥<EFBFBD>摨橒<E691A8>
<EFBFBD>𥕦遣<EFBFBD>交<EFBFBD>: 2026-01-03
**<2A><><EFBFBD>唳凒<E594B3>?: 2026-01-04
**<2A>嗆<EFBFBD>?: <20>?*撌脣<EFBFBD><EFBFBD>琜<EFBFBD><EFBFBD>非ify<EFBFBD><EFBFBD><EFBFBD>嚗?
**摰鮋<E691B0>撌乩<E6928C><E4B9A9>?: ~2憭抬<E686AD><E68AAC><EFBFBD><EFBFBD><EFBFBD>?+ Dify<66>亥<EFBFBD>摨橒<E691A8>
**<2A>詨<EFBFBD>隞瑕<E99A9E>?: PI<50>臬銁隡<E98A81><E99AA1>敺桐縑銝剛䌊<E5899B>嗅笆霂脲䰻霂㎞EDCap<61>笔<EFBFBD><E7AC94>唳旿 + <20>𠉛弦<F0A0899B>寞<EFBFBD><E5AF9E><EFBFBD>﹝
<EFBFBD>詨<EFBFBD><EFBFBD>𣂼停: <20>?REDCap<61>唳旿<E594B3><E697BF><EFBFBD> + <20>?銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹?+ <20>?閫<><E996AB>LLM撟餉<E6929F> + <20>?*Dify<EFBFBD>亥<EFBFBD>摨𤘪毽<EFBFBD><EFBFBD><EFBFBD>蝝?
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>翰<EFBFBD>笔鍳<E7AC94>剁<EFBFBD>1憭拐<E686AD>蝥選<E89DA5><E981B8>?<EFBFBD>𡁶鍂<EFBFBD>賢<EFBFBD>撅<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD> <20>滚之<E6BB9A>𤑳緵嚗𡁻<E59A97>𡁶鍂<F0A181B6>賢<EFBFBD>撅<EFBFBD>歇摰<E6AD87><E691B0>嚗?
撟喳蝱<EFBFBD>啁𠶖嚗?026-01-03靚<33><E99D9A>蝏𤘪<E89D8F>嚗㚁<E59A97>
- <EFBFBD>?LLMFactory 摰<><E691B0>撠梁貌嚗?蝘齿芋<E9BDBF>页<EFBFBD>DeepSeek/Qwen/GPT-5/Claude嚗㚁<E59A97><E39A81>蓥<EFBFBD>璅∪<E79285>嚗屸妟<E5B1B8>滨蔭
- <EFBFBD>?ChatContainer 摰<><E691B0>撠梁貌嚗鋫nt Design X蝏<58>辣嚗<E8BEA3>歇<EFBFBD>汽ool C撉諹<E69289>嚗ǚ968銵䕘<E98AB5>
- <EFBFBD>?<EFBFBD>臬<EFBFBD><EFBFBD>㗛<EFBFBD>撌脤<EFBFBD>蝵?*嚗䫤DEEPSEEK_API_KEY
<EFBFBD><EFBFBD>QWEN_API_KEY蝑?- <20>?<EFBFBD>鞟<EFBFBD>摰噼殿**嚗鋫SL<53><4C>C璅∪<E79285>撌脣之<E884A3>譍蝙<E8AD8D>剁<EFBFBD>蝔喳<E89D94><E596B3>舫<EFBFBD>
<EFBFBD>詨<EFBFBD>隡睃飵嚗?```typescript // <20>𡒊垢LLM靚<4D>鍂嚗?銵䔶誨<E494B6><E8AAA8><EFBFBD> import { LLMFactory } from '@/common/llm/adapters/LLMFactory.js'; const llm = LLMFactory.getAdapter('deepseek-v3'); const response = await llm.chat(messages, { temperature: 0.7 });
### <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>質<EFBFBD><E8B3AA>?
<EFBFBD>?Day 1嚗?-6撠𤩺𧒄嚗? 摰䂿緵<E482BF>箇<EFBFBD>撖寡<E69296> + 銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹?+ "typing"<22>漤<EFBFBD>
- 憭滨鍂LLMFactory嚗?撘<><E69298>𡢅<EFBFBD>
- <EFBFBD>𥕦遣ChatService.ts嚗?撠𤩺𧒄嚗? - <20>𥕦遣SessionMemory.ts嚗?撠𤩺𧒄嚗? - 靽格㺿WechatCallbackController嚗?撠𤩺𧒄嚗?<3F>?Day 2嚗?-6撠𤩺𧒄嚗? Dify<66>亥<EFBFBD>摨㯄<E691A8><E3AF84>?+ 瘛瑕<E7989B>璉<EFBFBD>蝝g<E89D9D>2026-01-04摰峕<E691B0>嚗? - <20>唾<EFBFBD>憿寧𤌍銝𥟠ify<66>亥<EFBFBD>摨橒<E691A8>1撠𤩺𧒄嚗? - <20><><EFBFBD>Dify璉<79>蝝W<E89D9D>ChatService嚗?撠𤩺𧒄嚗? - 靽桀<E99DBD><E6A180>誩㦛霂<E3A69B><E99C82>銝擧㺭<E693A7>格釣<E6A0BC>半ug嚗?撠𤩺𧒄嚗? - 蝡臬<E89DA1>蝡舀<E89DA1>霂蓥<E99C82><E893A5><EFBFBD>﹝霈啣<E99C88>嚗?撠𤩺𧒄嚗?<3F>?<3F><><EFBFBD>摰䂿緵: <20>冽𥁒<E586BD><F0A58192><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ool Calling
### <20><><EFBFBD><EFBFBD><EFBFBD>沲<EFBFBD><E6B2B2><EFBFBD>憭滨鍂<E6BBA8>𡁶鍂<F0A181B6>賢<EFBFBD>撅<EFBFBD><E69285>
PI<EFBFBD>鞾䔮 <20>?Node.js <20>?LLMFactory(deepseek-v3) <20>?<3F><><EFBFBD><EFBFBD>䂿<EFBFBD> <20>?隡<><E99AA1>敺桐縑<E6A190>券<EFBFBD>? <20>? <20>? SessionMemory RedcapAdapter嚗<72>虾<EFBFBD>㚁<EFBFBD>
**<2A>喲睸<E596B2>喟<EFBFBD>**嚗?- <20><> **憭滨鍂LLMFactory**嚗<>歇<EFBFBD>㚁<EFBFBD><E39A81>嗅<EFBFBD><E59785>𡢅<EFBFBD><F0A1A285>刻<EFBFBD>`deepseek-v3`嚗?- <20>?**<2A>芣䰻REDCap<61>唳旿**嚗<>歇<EFBFBD>兴edcapAdapter嚗<72><E59A97><EFBFBD>典朖<E585B8>荔<EFBFBD>
- <20>?**銝齿𦻖Dify**嚗<><E59A97>撠睲<E692A0>韏吔<E99F8F><E59094>惩翰撘<E7BFB0><E69298>𡢅<EFBFBD>
- <20>?**銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹?*嚗𠃊ode.js<6A><73><EFBFBD>嚗<EFBFBD><E59A97><EFBFBD><EFBFBD>餈?頧殷<E9A0A7>
- <20>?**甇<>銁颲枏<E9A2B2><E69E8F>漤<EFBFBD>**嚗<><E59A97><EFBFBD>喳<EFBFBD>"甇<>銁<EFBFBD>亥砭..."嚗?- <20>?**<2A>閙郊頝舐眏**嚗<><E59A97><EFBFBD>沖eAct敺芰㴓嚗?
**憸<>摯撌乩<E6928C><E4B9A9>誩之撟<E4B98B><E6929F>雿?*嚗?-3憭?<3F>?**1憭?*嚗<><E59A97>銝摔LM靚<4D>鍂撅<E98D82>歇摰<E6AD87><E691B0>嚗争黾
---
## <20>㴓 銝<><E98A9D><EFBFBD>瓲敹<E793B2>𤌍<EFBFBD><F0A48C8D><EFBFBD>隞瑕<E99A9E>?
### 1.1 銝箔<E98A9D>銋<EFBFBD><E98A8B>閬<EFBFBD>I撖寡<E69296><E5AFA1>賢<EFBFBD>嚗?
**敶枏<E695B6><E69E8F>嗆<EFBFBD>?*嚗㇄ay 3撌脣<E6928C><E884A3>琜<EFBFBD>嚗?```
<0A>?REDCap敶訫<E695B6><E8A8AB>唳旿 <20>?Node.js<6A>閗繮 <20>?隡<><E99AA1>敺桐縑<E6A190>券<EFBFBD><E588B8><EFBFBD>𡁶䰻
**<2A>格<EFBFBD><E6A0BC>嗆<EFBFBD>?*嚗㇊hase 1.5嚗㚁<EFBFBD>
<EFBFBD>?PI<50>其<EFBFBD>銝𡁜凝靽∩葉<E288A9>鞾䔮 <20>?AI<41><49>圾<EFBFBD>誩㦛 <20>?<3F>亥砭<E4BAA5>唳旿/<2F><>﹝ <20>?<3F>箄<EFBFBD><E7AE84>䂿<EFBFBD>
**<2A>詨<EFBFBD>隞瑕<E99A9E>?*嚗?- <20><> 銝餃𢆡<EFBFBD>亥砭嚗䥪I銝滨鍂蝑厰<EFBFBD>𡁶䰻嚗屸<EFBFBD><EFBFBD>園䔮"<22>啣銁<E595A3>亦<EFBFBD>憭𡁜<E686AD>鈭綽<E988AD>"
- <EFBFBD><EFBFBD> **<2A>唳旿蝛輸<E89D9B>?*嚗𡁜<E59A97><F0A1819C>嗆䰻霂㎞EDCap<61>唳旿嚗<E697BF><E59A97><EFBFBD><EFBFBD>祕<EFBFBD><E7A595><EFBFBD><EFBFBD>捶<EFBFBD>抒𠶖<E68A92><F0A0B696><EFBFBD>
- <EFBFBD><EFBFBD> **<2A>亥<EFBFBD>璉<EFBFBD>蝝?*嚗𡁏䰻霂Y<E99C82>蝛嗆䲮獢<E4B2AE><E78DA2><EFBFBD>RF銵冽聢<E586BD><E881A2><EFBFBD><EFBFBD>埝<EFBFBD><E59F9D>?- <20>働 <EFBFBD>箄<EFBFBD><EFBFBD><EFBFBD>圾嚗朞䌊<EFBFBD>嗉祗閮<EFBFBD><EFBFBD>鞾䔮嚗峕<EFBFBD><EFBFBD><EFBFBD>霈啣<EFBFBD><EFBFBD>賭誘
<EFBFBD><EFBFBD> 鈭䎚<E988AD><E48E9A><EFBFBD><EFBFBD>舀沲<E88880><E6B2B2><EFBFBD><EFBFBD>箔<EFBFBD><E7AE94>砍𧑐Dify嚗?
2.1 <20>港<EFBFBD><E6B8AF>嗆<EFBFBD><E59786>?
<EFBFBD>𢞖<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<3F>? PI (隡<><E99AA1>敺桐縑) <20>?<3F>? "P001<30><31><EFBFBD><EFBFBD>泵<EFBFBD><E6B3B5><EFBFBD><EFBFBD>埝<EFBFBD><E59F9D><EFBFBD><EFBFBD>嚗? <20>?<3F>婙<EFBFBD><E5A999><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>砂<EFBFBD><E7A082><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>? <20>? <20>?<3F>𢞖<EFBFBD><F0A29E96><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<3F>? Node.js <20>𡒊垢 (撌脫<E6928C> WechatCallbackController) <20>?<3F>? <20>𢞖<EFBFBD><F0A29E96><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>? <20>?<3F>? <20>?1. <20>交𤣰瘨<F0A4A3B0><E798A8> (handleCallback) <20>? <20>?<3F>? <20>?2. <20>誩㦛霂<E3A69B><E99C82> (Intent Router) <20>? <20>?<3F>? <20>?3. 撌亙<E6928C>靚<EFBFBD>鍂 (Tool Executor) <20>? <20>?<3F>? <20>?4. 隡<><E99AA1>敺桐縑<E6A190>券<EFBFBD>?(WechatService) <20>? <20>?<3F>? <20>婙<EFBFBD><E5A999><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>? <20>?<3F>婙<EFBFBD><E5A999><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>砂<EFBFBD><E7A082><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>? <20>? <20>𢞖<EFBFBD><F0A29E96><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>氯<EFBFBD><E6B0AF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>砂<EFBFBD><E7A082><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>? <20>? <20>? <20>?<3F>𢞖<EFBFBD><F0A29E96><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<3F>𢞖<EFBFBD><F0A29E96><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<3F>𢞖<EFBFBD><F0A29E96><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<3F>? Dify <20>?<3F>? REDCap <20>?<3F>? PostgreSQL <20>?<3F>? (<28>砍𧑐Docker)<29>?<3F>? API <20>?<3F>? <20>唳旿摨? <20>?<3F>? <20>?<3F>? <20>?<3F>? <20>?<3F>?- <20>𠉛弦<F0A0899B>寞<EFBFBD> <20>?<3F>?- <20><><EFBFBD><EFBFBD>㺭<EFBFBD>? <20>?<3F>?- 憿寧𤌍<E5AFA7>滨蔭 <20>?<3F>?- CRF銵冽聢 <20>?<3F>?- 韐冽綉<E586BD>嗆<EFBFBD>? <20>?<3F>?- 摰∟恣<E2889F>亙<EFBFBD> <20>?<3F>?- <20>冽𥁒敶埝﹝ <20>?<3F>?- 銝滩<E98A9D><E6BBA9>滚<EFBFBD> <20>?<3F>?- <20>冽<EFBFBD><E586BD>惩<EFBFBD> <20>?<3F>婙<EFBFBD><E5A999><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<3F>婙<EFBFBD><E5A999><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<3F>婙<EFBFBD><E5A999><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>? <50ms <100ms <20ms
<20>砍𧑐靚<F0A79190>鍂 <20>砍𧑐靚<F0A79190>鍂 <20>砍𧑐靚<F0A79190>鍂
2.2 <20>喲睸<E596B2><E79DB8><EFBFBD>臬<EFBFBD>蝑?
| <EFBFBD>喟<EFBFBD><EFBFBD>? | <EFBFBD>寞<EFBFBD> | <EFBFBD>笔<EFBFBD> |
|---|---|---|
| AI<EFBFBD>函<EFBFBD>撘閙<EFBFBD> | DeepSeek-V3 (API) | <EFBFBD>找遠瘥娪<EFBFBD>嚗峕𣈲<EFBFBD><EFBFBD>unction Calling |
| *<EFBFBD>亥<EFBFBD>摨? | Dify<EFBFBD>砍𧑐Docker | 撌脤<EFBFBD>蝵莎<EFBFBD><EFBFBD>𣳇<EFBFBD>憭𡝗<EFBFBD><EFBFBD>穿<EFBFBD>撱嗉<EFBFBD>雿? |
| *<EFBFBD>煾<EFBFBD><EFBFBD>唳旿摨? | Dify<EFBFBD><EFBFBD>蔭Weaviate | <EFBFBD>滨輕<EFBFBD>歹<EFBFBD>撘<EFBFBD>蝞勗朖<EFBFBD>? |
| 頝舐眏蝑𣇉裦 | <EFBFBD>閙郊<EFBFBD>誩㦛霂<EFBFBD><EFBFBD> | MVP<EFBFBD>嗆挾蝞<EFBFBD><EFBFBD>吔<EFBFBD>銝滨鍂ReAct敺芰㴓 |
| <EFBFBD>唳旿<EFBFBD>亥砭 | RedcapAdapter | 撌脫<EFBFBD>嚗𣬚凒<EFBFBD>亙<EFBFBD><EFBFBD>? |
<EFBFBD>?鈭䎚<E988AD><E48E9A><EFBFBD>蝞<EFBFBD><E89D9E><EFBFBD><EFBFBD><EFBFBD>𤏸恣<F0A48FB8>𡜐<EFBFBD>2憭抬<E686AD>
<EFBFBD>㴓 Day 1嚗𡁜抅蝖<E68A85>撖寡<E69296><E5AFA1>賢<EFBFBD>嚗?撠𤩺𧒄嚗?
<EFBFBD>詨<EFBFBD><EFBFBD>格<EFBFBD>
*霈呸I<EFBFBD>賢<EFBFBD>蝑𠉛鍂<EFBFBD>琿䔮憸矋<EFBFBD><EFBFBD>芣䰻REDCap<EFBFBD>唳旿嚗?
隞餃𦛚1.1嚗𡁜<EFBFBD>撱搴essionMemory嚗?0<><30><EFBFBD>嚗?
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤backend/src/modules/iit-manager/agents/SessionMemory.ts`
/**
* 隡朞<E99AA1>霈啣<E99C88>蝞∠<E89D9E><E288A0>剁<EFBFBD><E58981><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
* 摮睃<E691AE><E79D83>冽<EFBFBD><E586BD><EFBFBD>餈?頧桀笆霂嘅<E99C82><E59885>其<EFBFBD>銝𠹺<E98A9D><F0A0B9BA><EFBFBD><EFBFBD>閫? */
export class SessionMemory {
// <20><><EFBFBD>摮睃<E691AE>嚗㝯 userId: ConversationHistory }
private sessions: Map<string, ConversationHistory> = new Map();
private readonly MAX_HISTORY = 3; // <20>芯<EFBFBD><E88AAF>蹱<EFBFBD>餈?頧?
/**
* 瘛餃<E7989B>撖寡<E69296>霈啣<E99C88>
*/
addMessage(userId: string, role: 'user' | 'assistant', content: string): void {
if (!this.sessions.has(userId)) {
this.sessions.set(userId, {
userId,
messages: [],
createdAt: new Date(),
updatedAt: new Date()
});
}
const session = this.sessions.get(userId)!;
session.messages.push({
role,
content,
timestamp: new Date()
});
// <20>芯<EFBFBD><E88AAF>蹱<EFBFBD>餈?頧殷<E9A0A7>6<EFBFBD>⊥<EFBFBD><E28AA5>荔<EFBFBD>
if (session.messages.length > this.MAX_HISTORY * 2) {
session.messages = session.messages.slice(-this.MAX_HISTORY * 2);
}
session.updatedAt = new Date();
}
/**
* <20>瑕<EFBFBD><E79195>冽<EFBFBD>撖寡<E69296><E5AFA1><EFBFBD>蟮
*/
getHistory(userId: string): ConversationMessage[] {
const session = this.sessions.get(userId);
return session?.messages || [];
}
/**
* <20>瑕<EFBFBD><E79195>冽<EFBFBD>銝𠹺<E98A9D><F0A0B9BA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>餈睲<E9A488>頧殷<E9A0A7>
*/
getContext(userId: string): string {
const history = this.getHistory(userId);
if (history.length === 0) return '';
// <20>芸<EFBFBD><E88AB8><EFBFBD>餈睲<E9A488>頧桀笆霂? const recentMessages = history.slice(-2);
return recentMessages
.map(m => `${m.role}: ${m.content}`)
.join('\n');
}
/**
* 皜<>膄<EFBFBD>冽<EFBFBD>隡朞<E99AA1>
*/
clearSession(userId: string): void {
this.sessions.delete(userId);
}
/**
* 皜<><E79A9C>餈<EFBFBD><E9A488>隡朞<E99AA1>嚗<EFBFBD><E59A97>餈?撠𤩺𧒄<F0A4A9BA>芯蝙<E88AAF>剁<EFBFBD>
*/
cleanupExpiredSessions(): void {
const now = Date.now();
const ONE_HOUR = 3600000;
for (const [userId, session] of this.sessions.entries()) {
if (now - session.updatedAt.getTime() > ONE_HOUR) {
this.sessions.delete(userId);
}
}
}
}
interface ConversationHistory {
userId: string;
messages: ConversationMessage[];
createdAt: Date;
updatedAt: Date;
}
interface ConversationMessage {
role: 'user' | 'assistant';
content: string;
timestamp: Date;
}
// <20>典<EFBFBD><E585B8>蓥<EFBFBD>
export const sessionMemory = new SessionMemory();
// 摰𡁏𧒄皜<F0A79284><E79A9C>餈<EFBFBD><E9A488>隡朞<E99AA1>嚗<EFBFBD><E59A97>撠𤩺𧒄嚗?setInterval(() => {
sessionMemory.cleanupExpiredSessions();
}, 3600000);
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>臬<EFBFBD><E887AC>典笆霂嘥<E99C82><E598A5>?- <20>?<3F>航繮<E888AA>碶<EFBFBD>銝𧢲<E98A9D>
- <EFBFBD>?<3F>芸𢆡皜<F0A286A1><E79A9C>餈<EFBFBD><E9A488>隡朞<E99AA1>
隞餃𦛚1.2嚗𡁜<EFBFBD>撱慢hatService嚗?撠𤩺𧒄嚗争黾 憭滨鍂LLMFactory
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤backend/src/modules/iit-manager/services/ChatService.ts`
import { LLMFactory } from '../../../common/llm/adapters/LLMFactory.js';
import { Message } from '../../../common/llm/adapters/types.js';
import { logger } from '../../../common/logging/index.js';
import { sessionMemory } from '../agents/SessionMemory.js';
/**
* AI撖寡<E69296><E5AFA1>滚𦛚嚗<F0A69B9A><E59A97><EFBFBD>券<EFBFBD>𡁶鍂<F0A181B6>賢<EFBFBD>撅<EFBFBD>LMFactory嚗? * 憭<><E686AD>隡<EFBFBD><E99AA1>敺桐縑<E6A190>冽<EFBFBD>瘨<EFBFBD><E798A8>嚗峕𣈲<E5B395><F0A388B2><EFBFBD>銝𧢲<E98A9D>霈啣<E99C88>
*/
export class ChatService {
private llm;
constructor() {
// <20>?憭滨鍂<E6BBA8>𡁶鍂<F0A181B6>賢<EFBFBD>撅<EFBFBD>LMFactory嚗<79>妟<EFBFBD>滨蔭嚗? this.llm = LLMFactory.getAdapter('deepseek-v3');
}
/**
* 霂<><E99C82><EFBFBD>冽<EFBFBD><E586BD>誩㦛嚗<E3A69B>蒂銝𠹺<E98A9D><F0A0B9BA><EFBFBD><EFBFBD>
*/
async route(
userMessage: string,
userId: string,
projectId: string
): Promise<IntentRouteResult> {
try {
// 1. <20>瑕<EFBFBD>銝𠹺<E98A9D><F0A0B9BA>? const context = sessionMemory.getContext(userId);
logger.info('[SimpleIntentRouter] Routing with context', {
message: userMessage.substring(0, 50),
hasContext: !!context,
userId
});
// 2. <20><>遣Prompt嚗<74><E59A97><EFBFBD>思<EFBFBD>銝𧢲<E98A9D>嚗? const systemPrompt = this.buildSystemPrompt();
const userPrompt = context
? `<60>𣂷<EFBFBD>銝𧢲<E98A9D><F0A7A2B2>髢n${context}\n\n<EFBFBD>𣂼<EFBFBD><EFBFBD>漤䔮憸塩<EFBFBD>髢n${userMessage}`
: userMessage;
// 3. 靚<>鍂LLM
const response = await this.llm.chat.completions.create({
model: 'deepseek-chat',
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
],
tools: [this.getDataQueryTool()],
tool_choice: 'auto',
temperature: 0.1,
max_tokens: 500
});
const message = response.choices[0].message;
// 4. 憒<><E68692>LLM<4C>喳<EFBFBD>靚<EFBFBD>鍂撌亙<E6928C>
if (message.tool_calls && message.tool_calls.length > 0) {
const toolCall = message.tool_calls[0];
const toolArgs = JSON.parse(toolCall.function.arguments);
// <20>?銝𠹺<E98A9D><F0A0B9BA><EFBFBD>圾<EFBFBD>琜<EFBFBD>憒<EFBFBD><E68692>args銝剜<E98A9D>隞<EFBFBD><E99A9E>嚗<EFBFBD><E59A97>霂蓥<E99C82>銝𠹺<E98A9D><F0A0B9BA><EFBFBD>葉閫<E89189><E996AB>
if (context && this.hasPronouns(userMessage)) {
toolArgs.patient_id = this.extractPatientIdFromContext(context, toolArgs);
}
return {
needsToolCall: true,
toolName: 'query_clinical_data',
toolArgs,
rawResponse: message
};
}
// 5. <20>湔𦻖<E6B994>䂿<EFBFBD>
return {
needsToolCall: false,
directAnswer: message.content || '<27>望<EFBFBD>嚗峕<E59A97>瘝⊥<E7989D><E28AA5><EFBFBD>圾<EFBFBD>函<EFBFBD><E587BD>桅<EFBFBD>',
rawResponse: message
};
} catch (error: any) {
logger.error('[SimpleIntentRouter] Routing failed', {
error: error.message
});
return {
needsToolCall: false,
directAnswer: '<27>望<EFBFBD>嚗峕<E59A97><E5B395><EFBFBD><EFBFBD>鈭<EFBFBD><E988AD>鈭偦䔮憸矋<E686B8>霂瑞<E99C82><E7919E>𤾸<EFBFBD>霂?,
error: error.message
};
}
}
/**
* <20><>遣System Prompt
*/
private buildSystemPrompt(): string {
return `# 閫坿𠧧
雿䭾糓銝游<EFBFBD><EFBFBD>𠉛弦憿寧𤌍<EFBFBD>拇<EFBFBD>嚗<EFBFBD>葬<EFBFBD>周I<EFBFBD>亥砭憿寧𤌍<EFBFBD>唳旿<EFBFBD>?
# <20>賢<EFBFBD>
雿惩虾隞交䰻霂㎞EDCap<EFBFBD>唳旿摨橒<EFBFBD><EFBFBD><EFBFBD>𡠺嚗?1. 憿寧𤌍蝏蠘恣嚗<E681A3><E59A97>蝏<EFBFBD>犖<EFBFBD>啜<EFBFBD><E5959C>㺭<EFBFBD>桀<EFBFBD><E6A180>渡<EFBFBD>嚗?2. <20><><EFBFBD><EFBFBD>祕<EFBFBD><E7A595><EFBFBD>敶訫<E695B6><E8A8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>抅<EFBFBD>砌縑<E7A08C>荔<EFBFBD>
3. 韐冽綉<E586BD>嗆<EFBFBD><E59786><EFBFBD><EFBFBD>唳旿<E594B3>桅<EFBFBD>嚗?
# 銝𠹺<E98A9D><F0A0B9BA><EFBFBD><EFBFBD>閫?- 憒<><E68692><EFBFBD>冽<EFBFBD>霂?隞?<3F>?餈嗘葵<E59798><E891B5><EFBFBD>?蝑劐誨霂㵪<E99C82>霂瑟覔<E7919F>柴<EFBFBD>𣂷<EFBFBD>銝𧢲<E98A9D><F0A7A2B2>睲葉<E79DB2>𣂼<EFBFBD><F0A382BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?- 憒<><E68692>銝𠹺<E98A9D><F0A0B9BA><EFBFBD>葉瘝⊥<E7989D><E28AA5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>瘀<EFBFBD>霂瑁<E99C82>瘙<EFBFBD>鍂<EFBFBD>瑟<EFBFBD>靘?
# 蝥行<E89DA5>
- 銝亦<E98A9D>蝻㚚<E89DBB>䭾㺭<E4ADBE>?- <20>芾<EFBFBD><E88ABE>亥砭REDCap<61>唳旿嚗䔶<E59A97><E494B6>賣䰻霂X<E99C82>獢?- <20>䂿<EFBFBD>閬<EFBFBD><E996AC>瘣<EFBFBD><E798A3>銝䫤;
}
/**
* 摰帋<E691B0><E5B88B>唳旿<E594B3>亥砭撌亙<E6928C>
*/
private getDataQueryTool(): any {
return {
type: "function",
function: {
name: "query_clinical_data",
description: `<60>亥砭REDCap銝游<E98A9D><E6B8B8>唳旿<E594B3>?<3F><>鍂<EFBFBD>箸艶嚗?- <20>桅★<E6A185>桃<EFBFBD>霈∴<E99C88><E288B4>啣銁<E595A3>亦<EFBFBD>憭𡁜<E686AD>鈭綽<E988AD><E7B6BD>唳旿韐券<E99F90>憒<EFBFBD><E68692>嚗?- <20>格<EFBFBD><E6A0BC><EFBFBD>祕<EFBFBD><E7A595><EFBFBD>P001<30><31><EFBFBD><EFBFBD><EFBFBD>摰䔶<E691B0><E494B6>梹<EFBFBD><E6A2B9>劐<EFBFBD><E58A90>臬<EFBFBD>摨𥪜<E691A8>嚗?- <20>株捶<E6A0AA>抒𠶖<E68A92><F0A0B696><EFBFBD><EFBFBD>匧𪑛鈭𥡝捶<F0A5A19D>折䔮憸矋<E686B8>`,
parameters: {
type: "object",
properties: {
intent: {
type: "string",
enum: ["project_stats", "patient_detail", "qc_status"],
description: `<60>亥砭<E4BAA5>誩㦛嚗?- project_stats: 憿寧𤌍蝏蠘恣
- patient_detail: <20><><EFBFBD><EFBFBD>祕<EFBFBD>?- qc_status: 韐冽綉<E586BD>嗆<EFBFBD><E59786>
},
patient_id: {
type: "string",
description: "<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>瘀<EFBFBD>憒<EFBFBD>001嚗㚁<E59A97>敶𧗽ntent=patient_detail<69>嗅<EFBFBD>憛?
}
},
required: ["intent"]
}
}
};
}
/**
* 璉<><E79289>交<EFBFBD><E4BAA4>臭葉<E887AD>臬炏<E887AC>劐誨霂? */
private hasPronouns(message: string): boolean {
const pronouns = ['隞?, '憟?, '餈嗘葵<EFBFBD><EFBFBD><EFBFBD>?, '霂交<E99C82><E4BAA4>?, '餈嗘<EFBFBD>', '<EFBFBD><EFBFBD><EFBFBD>'];
return pronouns.some(p => message.includes(p));
}
/**
* 隞𦒘<E99A9E>銝𧢲<E98A9D>銝剜<E98A9D><E5899C>𡝗<EFBFBD><F0A19D97><EFBFBD>D
*/
private extractPatientIdFromContext(context: string, toolArgs: any): string {
// 蝞<><E89D9E>閙迤<E99699>蹱<EFBFBD><E8B9B1>𡝗<EFBFBD><F0A19D97><EFBFBD><EFBFBD><EFBFBD>? const match = context.match(/P\d{3,}/);
return match ? match[0] : toolArgs.patient_id;
}
}
export interface IntentRouteResult {
needsToolCall: boolean;
toolName?: string;
toolArgs?: any;
directAnswer?: string;
error?: string;
rawResponse?: any;
}
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>航<EFBFBD><E888AA>急䰻霂X<E99C82><EFBCB8>?- <20>?<3F>舀<EFBFBD>銝𠹺<E98A9D><F0A0B9BA><EFBFBD><EFBFBD>閫<EFBFBD><E996AB>隞<EFBFBD><E99A9E>閫<EFBFBD><E996AB>嚗?- <20>?<3F>躰秤憭<E7A7A4><E686AD>摰<EFBFBD><E691B0>
隞餃𦛚1.3嚗𡁶<EFBFBD><EFBFBD>巁oolExecutor嚗?.5撠𤩺𧒄嚗?
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤backend/src/modules/iit-manager/agents/SimpleToolExecutor.ts`
import { RedcapAdapter } from '../adapters/RedcapAdapter.js';
import { logger } from '../../../common/logging/index.js';
import { prisma } from '../../../config/database.js';
/**
* 蝞<><E89D9E>𣇉<EFBFBD>撌亙<E6928C><E4BA99>扯<EFBFBD><E689AF>? * <20>芣<EFBFBD>銵朙EDCap<61>唳旿<E594B3>亥砭
*/
export class SimpleToolExecutor {
/**
* <20>扯<EFBFBD><E689AF>亥砭銝游<E98A9D><E6B8B8>唳旿
*/
async execute(
toolArgs: {
intent: 'project_stats' | 'patient_detail' | 'qc_status';
patient_id?: string;
},
context: {
projectId: string;
userId: string;
}
): Promise<ToolExecutionResult> {
try {
logger.info('[SimpleToolExecutor] Executing query', {
intent: toolArgs.intent,
patientId: toolArgs.patient_id,
projectId: context.projectId
});
// 1. <20>瑕<EFBFBD>憿寧𤌍<E5AFA7>滨蔭
const project = await prisma.iitProject.findUnique({
where: { id: context.projectId },
select: {
name: true,
redcapApiUrl: true,
redcapApiToken: true
}
});
if (!project) {
return {
success: false,
data: null,
error: '憿寧𤌍銝滚<E98A9D><E6BB9A>?
};
}
// 2. <20>嘥<EFBFBD><E598A5>餸edcapAdapter
const redcap = new RedcapAdapter(
project.redcapApiUrl,
project.redcapApiToken
);
// 3. <20>寞旿intent<6E>扯<EFBFBD><E689AF>亥砭
switch (toolArgs.intent) {
case 'project_stats':
return await this.getProjectStats(redcap, project.name);
case 'patient_detail':
if (!toolArgs.patient_id) {
return {
success: false,
data: null,
error: '霂瑟<EFBFBD>靘𥟇<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>瘀<EFBFBD>憒<EFBFBD><EFBFBD>P001嚗?
};
}
return await this.getPatientDetail(redcap, toolArgs.patient_id);
case 'qc_status':
return await this.getQCStatus(context.projectId);
default:
return {
success: false,
data: null,
error: '<27>芰䰻<E88AB0><E4B0BB>䰻霂Y掩<EFBCB9>?
};
}
} catch (error: any) {
logger.error('[SimpleToolExecutor] Execution failed', {
error: error.message
});
return {
success: false,
data: null,
error: error.message
};
}
}
/**
* <20>瑕<EFBFBD>憿寧𤌍蝏蠘恣
*/
private async getProjectStats(
redcap: RedcapAdapter,
projectName: string
): Promise<ToolExecutionResult> {
const records = await redcap.exportRecords();
return {
success: true,
data: {
type: 'project_stats',
projectName,
stats: {
totalRecords: records.length,
enrolled: records.length,
completed: records.filter((r: any) => r.complete === '2').length,
dataQuality: this.calculateDataQuality(records)
}
}
};
}
/**
* <20>瑕<EFBFBD><E79195><EFBFBD><EFBFBD><EFBFBD>祕<EFBFBD>? */
private async getPatientDetail(
redcap: RedcapAdapter,
patientId: string
): Promise<ToolExecutionResult> {
const records = await redcap.exportRecords([patientId]);
if (records.length === 0) {
return {
success: false,
data: null,
error: `<60>芣𪄳<E88AA3>唳<EFBFBD><E594B3>?${patientId}`
};
}
const record = records[0];
return {
success: true,
data: {
type: 'patient_detail',
patientId,
details: {
age: record.age,
gender: record.gender,
bmi: record.bmi,
complete: record.complete === '2' ? '撌脣<EFBFBD><EFBFBD>? : '餈𥡝<E9A488>銝?,
lastUpdate: new Date().toISOString()
}
}
};
}
/**
* <20>瑕<EFBFBD>韐冽綉<E586BD>嗆<EFBFBD>? */
private async getQCStatus(projectId: string): Promise<ToolExecutionResult> {
const logs = await prisma.iitAuditLog.findMany({
where: {
projectId,
actionType: 'quality_issue'
},
orderBy: { createdAt: 'desc' },
take: 10
});
return {
success: true,
data: {
type: 'qc_status',
issueCount: logs.length,
recentIssues: logs.map(log => ({
recordId: log.entityId,
issue: log.details,
createdAt: log.createdAt
}))
}
};
}
/**
* 霈∠<E99C88><E288A0>唳旿韐券<E99F90>嚗<EFBFBD><E59A97><EFBFBD>閧<EFBFBD>瘜𤏪<E7989C>
*/
private calculateDataQuality(records: any[]): string {
if (records.length === 0) return '0%';
const completedCount = records.filter((r: any) => r.complete === '2').length;
const quality = (completedCount / records.length) * 100;
return `${quality.toFixed(1)}%`;
}
}
export interface ToolExecutionResult {
success: boolean;
data: any;
error?: string;
}
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>舀䰻霂a★<EFBD81>桃<EFBFBD>霈?- <20>?<3F>舀䰻霂X<E99C82><EFBCB8><EFBFBD>祕<EFBFBD>?- <20>?<3F>舀䰻霂Z捶<EFBCBA>抒𠶖<E68A92>?
隞餃𦛚1.4嚗𡁶<EFBFBD><EFBFBD>䨝nswerGenerator嚗?撠𤩺𧒄嚗?
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤backend/src/modules/iit-manager/agents/SimpleAnswerGenerator.ts`
import { ToolExecutionResult } from './SimpleToolExecutor.js';
import { logger } from '../../../common/logging/index.js';
/**
* 蝞<><E89D9E>𣇉<EFBFBD>蝑娍<E89D91><E5A88D><EFBFBD><EFBFBD><EFBFBD>? * 雿輻鍂璅⊥踎<E28AA5><E8B88E><EFBFBD><EFBFBD>䂿<EFBFBD>嚗䔶<E59A97>靚<EFBFBD>鍂LLM嚗<4D><E59A97><EFBFBD><EFBFBD><EFBFBD><EFBFBD>穿<EFBFBD>
*/
export class SimpleAnswerGenerator {
/**
* <20><><EFBFBD><EFBFBD>䂿<EFBFBD>
*/
generate(
userQuestion: string,
toolResult: ToolExecutionResult
): string {
try {
logger.info('[SimpleAnswerGenerator] Generating answer', {
success: toolResult.success,
dataType: toolResult.data?.type
});
// 憒<><E68692>撌亙<E6928C><E4BA99>扯<EFBFBD>憭梯揖
if (!toolResult.success) {
return this.generateErrorMessage(toolResult.error);
}
// <20>寞旿<E5AF9E>唳旿蝐餃<E89D90><E9A483><EFBFBD><EFBFBD><EFBFBD>䂿<EFBFBD>
const dataType = toolResult.data.type;
if (dataType === 'project_stats') {
return this.generateProjectStatsAnswer(toolResult.data);
} else if (dataType === 'patient_detail') {
return this.generatePatientDetailAnswer(toolResult.data);
} else if (dataType === 'qc_status') {
return this.generateQCStatusAnswer(toolResult.data);
}
return '<27>望<EFBFBD>嚗峕<E59A97><E5B395>䭾<EFBFBD><E4ADBE><EFBFBD><EFBFBD><EFBFBD>䂿<EFBFBD>';
} catch (error: any) {
logger.error('[SimpleAnswerGenerator] Generation failed', {
error: error.message
});
return '<27>望<EFBFBD>嚗<EFBFBD><E59A97>蝑𠉛<E89D91><F0A0899B>𣂼仃韐?;
}
}
/**
* <20><><EFBFBD>憿寧𤌍蝏蠘恣<E8A098>䂿<EFBFBD>
*/
private generateProjectStatsAnswer(data: any): string {
const stats = data.stats;
return `<60><> **${data.projectName}憿寧𤌍蝏蠘恣**
<EFBFBD>?**<2A>亦<EFBFBD>鈭箸㺭**嚗?{stats.enrolled}靘?<3F>?**摰峕<E691B0><E5B395><EFBFBD><EFBFBD>**嚗?{stats.completed}靘?<3F>?**<2A>唳旿韐券<E99F90>**嚗?{stats.dataQuality}
<EFBFBD>働 <20>湔鰵<E6B994>園𡢿嚗?{new Date().toLocaleString('zh-CN')}`;
}
/**
* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>祕<EFBFBD><E7A595><EFBFBD>蝑? */
private generatePatientDetailAnswer(data: any): string {
const details = data.details;
return `<60>𪈠 **<2A><><EFBFBD>?${data.patientId} 霂行<E99C82>**
<EFBFBD><EFBFBD> **<2A>箸𧋦靽⊥<E99DBD>**嚗?- 撟湧<E6929F>嚗?{details.age || '<EFBFBD>芸<EFBFBD><EFBFBD>?}撗?- <EFBFBD>批<EFBFBD>嚗?{details.gender || '<27>芸<EFBFBD><E88AB8>?}
- BMI嚗?{details.bmi || '<EFBFBD>芸<EFBFBD><EFBFBD>?}
<EFBFBD><EFBFBD> **敶訫<EFBFBD><EFBFBD>嗆<EFBFBD>?*嚗?- ${details.complete}
<EFBFBD>働 <EFBFBD><EFBFBD><EFBFBD>擧凒<EFBFBD>堆<EFBFBD>${new Date().toLocaleString('zh-CN')}`;
}
/**
* <20><><EFBFBD>韐冽綉<E586BD>嗆<EFBFBD><E59786><EFBFBD>蝑? */
private generateQCStatusAnswer(data: any): string {
const issues = data.recentIssues.slice(0, 5);
let answer = `<EFBFBD><EFBFBD> **韐冽綉<EFBFBD>嗆<EFBFBD>?*\n\n`;
answer += `<EFBFBD>𩤃<EFBFBD> **韐冽綉<EFBFBD>桅<EFBFBD><EFBFBD>?*嚗?{data.issueCount}銝歿n\n`;
if (issues.length > 0) {
answer += `<EFBFBD><EFBFBD> **<EFBFBD><EFBFBD>餈煾䔮憸?*嚗䨵n`;
issues.forEach((issue: any, index: number) => {
answer += `${index + 1}. 霈啣<EFBFBD>${issue.recordId}\n`;
});
} else {
answer += `<EFBFBD>?<EFBFBD><EFBFBD><EFBFBD>韐冽綉<EFBFBD>桅<EFBFBD>`;
}
return answer;
}
/**
* <20><><EFBFBD><EFBFBD>躰秤<E8BAB0>鞟內
*/
private generateErrorMessage(error?: string): string {
return `<EFBFBD>?<EFBFBD>亥砭憭梯揖
<EFBFBD>笔<EFBFBD>嚗?{error || '<27>芰䰻<E88AB0>躰秤'}
<EFBFBD>働 <EFBFBD>典虾隞伐<EFBFBD>
1. 蝔滚<EFBFBD><EFBFBD>滩<EFBFBD>
2. <EFBFBD>V葵<EFBFBD>格<EFBFBD>
3. <EFBFBD>𠉛頂蝞∠<EFBFBD><EFBFBD>𧜶;
}
}
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>䂿<EFBFBD><E482BF>澆<EFBFBD><E6BE86>见末
- <EFBFBD>?<3F>舀<EFBFBD>Markdown
- <EFBFBD>?<3F>躰秤<E8BAB0>鞟內皜<E585A7>苊
隞餃𦛚1.5嚗𡁻<EFBFBD><EFBFBD>𣂼<EFBFBD>WechatCallbackController嚗?撠𤩺𧒄嚗?
靽格㺿<EFBFBD><EFBFBD>辣嚗䫤backend/src/modules/iit-manager/controllers/WechatCallbackController.ts`
**<2A>玖andleCallback<63>寞<EFBFBD>銝剜溶<E5899C>?*嚗?
import { sessionMemory } from '../agents/SessionMemory.js';
import { SimpleIntentRouter } from '../agents/SimpleIntentRouter.js';
import { SimpleToolExecutor } from '../agents/SimpleToolExecutor.js';
import { SimpleAnswerGenerator } from '../agents/SimpleAnswerGenerator.js';
class WechatCallbackController {
private intentRouter: SimpleIntentRouter;
private toolExecutor: SimpleToolExecutor;
private answerGenerator: SimpleAnswerGenerator;
constructor() {
// ... <20>唳<EFBFBD>隞<EFBFBD><E99A9E> ...
this.intentRouter = new SimpleIntentRouter();
this.toolExecutor = new SimpleToolExecutor();
this.answerGenerator = new SimpleAnswerGenerator();
}
async handleCallback(request: FastifyRequest, reply: FastifyReply): Promise<void> {
// ... <20>唳<EFBFBD><E594B3><EFBFBD><EFBFBD>霂<EFBFBD><E99C82><EFBFBD>圾撖<E59CBE><E69296>餉<EFBFBD> ...
// <20>?蝡见朖餈𥪜<E9A488>success
reply.send('success');
// <20>?撘<>郊憭<E9838A><E686AD>嚗<EFBFBD>鰵憓麫I撖寡<E69296>嚗? setImmediate(async () => {
try {
const userMessage = decryptedData.Content;
const userId = decryptedData.FromUserName;
logger.info('<27>𢬢 <20>嗅<EFBFBD><E59785>冽<EFBFBD>瘨<EFBFBD><E798A8>', {
userId,
message: userMessage.substring(0, 50)
});
// <20>?蝡见朖<E8A781>煾<EFBFBD>?甇<>銁<EFBFBD>亥砭..."<22>漤<EFBFBD>
await wechatService.sendTextMessage(
userId,
'<27>哄 甇<>銁<EFBFBD>亥砭嚗諹窈蝔滚<E89D94>?..'
);
// 1. 靽嘥<E99DBD><E598A5>冽<EFBFBD>瘨<EFBFBD><E798A8><EFBFBD>唬<EFBFBD>霂肽扇敹? sessionMemory.addMessage(userId, 'user', userMessage);
// 2. <20>瑕<EFBFBD><E79195>冽<EFBFBD><E586BD><EFBFBD>★<EFBFBD>桐縑<E6A190>? const userMapping = await prisma.iitUserMapping.findFirst({
where: { wechatUserId: userId }
});
if (!userMapping) {
await wechatService.sendTextMessage(
userId,
'<27>𩤃<EFBFBD> <20>刻<EFBFBD><E588BB>芰<EFBFBD>摰𡁻★<F0A181BB>殷<EFBFBD>霂瑁<E99C82>蝟餌恣<E9A48C><E681A3><EFBFBD><EFBFBD>滨蔭'
);
return;
}
// 3. <20>誩㦛霂<E3A69B><E99C82>嚗<EFBFBD>蒂銝𠹺<E98A9D><F0A0B9BA><EFBFBD><EFBFBD>
const routeResult = await this.intentRouter.route(
userMessage,
userId,
userMapping.projectId
);
// 4. 憒<><E68692><EFBFBD>湔𦻖<E6B994>䂿<EFBFBD>
if (!routeResult.needsToolCall) {
const answer = routeResult.directAnswer!;
await wechatService.sendTextMessage(userId, answer);
sessionMemory.addMessage(userId, 'assistant', answer);
return;
}
// 5. <20>扯<EFBFBD>撌亙<E6928C>
const toolResult = await this.toolExecutor.execute(
routeResult.toolArgs,
{
projectId: userMapping.projectId,
userId
}
);
// 6. <20><><EFBFBD><EFBFBD>䂿<EFBFBD>
const answer = this.answerGenerator.generate(userMessage, toolResult);
// 7. <20>煾<EFBFBD><E785BE><EFBFBD>憭? await wechatService.sendMarkdownMessage(userId, answer);
// 8. 靽嘥<E99DBD>AI<41>䂿<EFBFBD><E482BF>唬<EFBFBD>霂肽扇敹? sessionMemory.addMessage(userId, 'assistant', answer);
// 9. 霈啣<E99C88>摰∟恣<E2889F>亙<EFBFBD>
await prisma.iitAuditLog.create({
data: {
projectId: userMapping.projectId,
actionType: 'wechat_user_query',
operator: userId,
entityId: userId,
details: {
question: userMessage,
answer: answer.substring(0, 200),
toolUsed: 'query_clinical_data',
hasContext: !!sessionMemory.getContext(userId)
}
}
});
logger.info('<27>?<3F>䂿<EFBFBD><E482BF>煾<EFBFBD><E785BE><EFBFBD><EFBFBD>?, { userId });
} catch (error: any) {
logger.error('<EFBFBD>?憭<EFBFBD><EFBFBD><EFBFBD>冽<EFBFBD>瘨<EFBFBD><EFBFBD>憭梯揖', {
error: error.message
});
await wechatService.sendTextMessage(
userId,
'<EFBFBD>望<EFBFBD>嚗峕<EFBFBD><EFBFBD><EFBFBD><EFBFBD>鈭<EFBFBD><EFBFBD>鈭偦䔮憸矋<EFBFBD>霂瑞<EFBFBD><EFBFBD>𤾸<EFBFBD>霂?
);
}
});
}
}
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>舀𦻖<E88880>嗥鍂<E597A5>瑟<EFBFBD><E7919F>?- <20>?蝡见朖<E8A781>煾<EFBFBD>?甇<>銁<EFBFBD>亥砭..."
- <EFBFBD>?甇<>&霂<EFBC86><E99C82><EFBFBD>誩㦛
- <EFBFBD>?甇<>&<EFBFBD>扯<EFBFBD>撌亙<E6928C>
- <EFBFBD>?甇<>&<EFBFBD>煾<EFBFBD><E785BE><EFBFBD>憭?- <20>?銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹<E68987><E695B9><EFBFBD>?
<EFBFBD>㴓 Day 2嚗帋<E59A97>銝𧢲<E98A9D>隡睃<E99AA1> + 瘚贝<E7989A>嚗?撠𤩺𧒄嚗?
隞餃𦛚2.1嚗帋<EFBFBD>銝𧢲<EFBFBD>霈啣<EFBFBD>隡睃<EFBFBD>嚗?撠𤩺𧒄嚗?
**憓𧼮撩SessionMemory嚗峕𣈲<E5B395><F0A388B2><EFBFBD><EFBFBD><EFBFBD>D<EFBFBD>𣂼<EFBFBD>**嚗?
// <20>沒essionMemory銝剜溶<E5899C>?/**
* 隞𤾸<EFBFBD><EFBFBD>脰扇敶蓥葉<EFBFBD>𣂼<EFBFBD><EFBFBD><EFBFBD>餈烐<EFBFBD><EFBFBD>啁<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>D
*/
getLastPatientId(userId: string): string | null {
const history = this.getHistory(userId);
// 隞擧<E99A9E>餈𤑳<E9A488>撖寡<E69296>銝剖<E98A9D>鍦<EFBFBD><E98DA6>交𪄳<E4BAA4><F0AA84B3><EFBFBD><EFBFBD>D
for (let i = history.length - 1; i >= 0; i--) {
const message = history[i];
const match = message.content.match(/P\d{3,}/);
if (match) {
return match[0];
}
}
return null;
}
**<2A>沒impleIntentRouter銝凋蝙<E5878B>?*嚗?
// 憒<><E68692><EFBFBD>冽<EFBFBD>霂?隞𡝗<E99A9E>銝滩<E98A9D><E6BBA9>滚<EFBFBD><E6BB9A>梹<EFBFBD>"嚗諹䌊<E8ABB9>典‵<E585B8><E280B5>atient_id
if (context && this.hasPronouns(userMessage) && !toolArgs.patient_id) {
const lastPatientId = sessionMemory.getLastPatientId(userId);
if (lastPatientId) {
toolArgs.patient_id = lastPatientId;
logger.info('[SimpleIntentRouter] <20>芸𢆡憛怠<E6869B><E680A0><EFBFBD><EFBFBD><EFBFBD>D', {
patientId: lastPatientId
});
}
}
隞餃𦛚2.2嚗𡁜<EFBFBD><EFBFBD>湔<EFBFBD>霂𤏪<EFBFBD>3撠𤩺𧒄嚗?
瘚贝<EFBFBD><EFBFBD>箸艶嚗?
// <20>箸艶1嚗𡁏<E59A97>銝𠹺<E98A9D><F0A0B9BA><EFBFBD>䰻霂?{
input: "<22>啣銁<E595A3>亦<EFBFBD>憭𡁜<E686AD>鈭綽<E988AD>",
expectedIntent: "project_stats",
expectedOutput: "<22><> 憿寧𤌍蝏蠘恣\n<>?<3F>亦<EFBFBD>鈭箸㺭嚗䧥X靘?
}
// <20>箸艶2嚗𡁏<E59A97>銝𠹺<E98A9D><F0A0B9BA><EFBFBD><EFBFBD>憭朞蔭撖寡<E69296>嚗<EFBFBD><E59A97><EFBFBD>殷<EFBFBD>嚗?{
conversation: [
{
input: "撣格<EFBFBD><EFBFBD>乩<EFBFBD>銝閪001<EFBFBD><EFBFBD><EFBFBD><EFBFBD>?,
expectedIntent: "patient_detail",
expectedPatientId: "P001"
},
{
input: "隞𡝗<E99A9E>銝滩<E98A9D><E6BBA9>滚<EFBFBD><E6BB9A>梹<EFBFBD>", // <20>?隞<><E99A9E>"隞?
expectedIntent: "patient_detail",
expectedPatientId: "P001", // <20>?<3F>芸𢆡憛怠<E6869B>
expectedOutput: "摨磰砲<E7A3B0><E7A0B2>鉄P001"
}
]
}
// <20>箸艶3嚗𡁏迤<F0A1818F>刻<EFBFBD><E588BB>亙<EFBFBD>擐?{
input: "<22>啣銁<E595A3>亦<EFBFBD>憭𡁜<E686AD>鈭綽<E988AD>",
expectedFirstReply: "<22>哄 甇<>銁<EFBFBD>亥砭嚗諹窈蝔滚<E89D94>?..",
expectedSecondReply: "<22><> 憿寧𤌍蝏蠘恣..."
}
// <20>箸艶4嚗朞捶<E69C9E>扳䰻霂?{
input: "<22>匧𪑛鈭𥡝捶<F0A5A19D>折䔮憸矋<E686B8>",
expectedIntent: "qc_status",
expectedOutput: "<22><> 韐冽綉<E586BD>嗆<EFBFBD>?
}
// <20>箸艶5嚗𡁻𤦭<F0A181BB>?{
input: "雿惩末",
expectedOutput: "<EFBFBD>典末嚗<EFBFBD><EFBFBD><EFBFBD>臭葩摨羓<EFBFBD>蝛嗅𨭌<EFBFBD>?
}
瘚贝<EFBFBD>甇仿炊嚗?1. <20>其<EFBFBD>銝𡁜凝靽∩葉<E288A9>煾<EFBFBD><E785BE><EFBFBD>霂閙<E99C82><E99699>?2. 撉諹<E69289><E8ABB9>臬炏<E887AC>嗅<EFBFBD>"甇<>銁<EFBFBD>亥砭..." 3. 撉諹<E69289><E8ABB9><EFBFBD>蝏<EFBFBD><E89D8F>憭滚<E686AD>摰?4. 璉<><E79289>亙恣霈⊥𠯫敹𦯀葉<F0A6AF80><E89189><EFBFBD>銝𧢲<E98A9D><F0A7A2B2><EFBFBD>扇 5. 瘚贝<E7989A>憭朞蔭撖寡<E69296><E5AFA1><EFBFBD><EFBFBD>銝𧢲<E98A9D><F0A7A2B2><EFBFBD>圾
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?5銝芣<E98A9D>霂訫㦤<E8A8AB>臬<EFBFBD><E887AC>券<EFBFBD>朞<EFBFBD>
- <EFBFBD>?"甇<>銁颲枏<E9A2B2>"<22>漤<EFBFBD><E6BCA4><EFBFBD><EFBFBD>
- <EFBFBD>?銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹<E68987><E695B9><EFBFBD><EFBFBD><EFBFBD>隞<EFBFBD><E99A9E>閫<EFBFBD><E996AB>嚗?- <20>?<3F>𧼮<EFBFBD><F0A7BCAE>園𡢿<3蝘?
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?
| <EFBFBD>蠘<EFBFBD> | 撉峕𤣰<EFBFBD><EFBFBD><EFBFBD> | 隡睃<EFBFBD>蝥? |
|---|---|---|
| <EFBFBD>箇<EFBFBD>撖寡<EFBFBD> | <EFBFBD>舀䰻霂㎞EDCap<EFBFBD>唳旿 | <EFBFBD>𣞁 P0 |
| *銝𠹺<EFBFBD><EFBFBD><EFBFBD>扇敹? | <EFBFBD>舀<EFBFBD><EFBFBD><EFBFBD>餈?頧桀笆霂? | <EFBFBD>𣞁 P0 |
| 隞<EFBFBD><EFBFBD>閫<EFBFBD><EFBFBD> | "隞?<3F>質䌊<E8B3AA>刻<EFBFBD><E588BB>急<EFBFBD><E680A5>? | <EFBFBD>𣞁 P0 |
| 甇<EFBFBD>銁颲枏<EFBFBD><EFBFBD>漤<EFBFBD> | 蝡见朖<EFBFBD>?甇<>銁<EFBFBD>亥砭..." | <EFBFBD>𣞁 P0 |
| <EFBFBD>𧼮<EFBFBD>撱嗉<EFBFBD> | <3蝘? | <EFBFBD>𣞁 P0 |
| *<EFBFBD>誩㦛霂<EFBFBD><EFBFBD><EFBFBD><EFBFBD>&<EFBFBD>? | >80% | <EFBFBD>𣞁 P0 |
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>ǒs摰峕㟲<E5B395><E39FB2>笆瘥?
| <EFBFBD>蠘<EFBFBD> | <EFBFBD><EFBFBD><EFBFBD><EFBFBD>?(2憭? | 摰峕㟲<EFBFBD>?(5憭? |
|---|---|---|
| REDCap<EFBFBD>亥砭 | <EFBFBD>? | <EFBFBD>? |
| 銝𠹺<EFBFBD><EFBFBD><EFBFBD>扇敹? | <EFBFBD>?(<28><><EFBFBD>3頧? | <EFBFBD>?(<28><><EFBFBD>3頧? |
| 甇<EFBFBD>銁颲枏<EFBFBD><EFBFBD>漤<EFBFBD> | <EFBFBD>? | <EFBFBD>? |
| Dify<EFBFBD>亥<EFBFBD>摨? | <EFBFBD>? | <EFBFBD>? |
| <EFBFBD>冽𥁒<EFBFBD>芸𢆡敶埝﹝ | <EFBFBD>? | <EFBFBD>? |
| <EFBFBD><EFBFBD>﹝<EFBFBD>亥砭 | <EFBFBD>? | <EFBFBD>? |
<EFBFBD><EFBFBD>儭?銝剹<E98A9D><E589B9><EFBFBD><EFBFBD>渡<EFBFBD>撘<EFBFBD><E69298>𤏸恣<F0A48FB8>𡜐<EFBFBD>5憭抬<E686AD><E68AAC>舫<EFBFBD>㚁<EFBFBD>
Day 1嚗鋽ify<66>臬<EFBFBD><E887AC>滨蔭銝𡒊䰻霂<E4B0BB><E99C82><EFBFBD>𥕦遣嚗?撠𤩺𧒄嚗?
隞餃𦛚1.1嚗𡁻<EFBFBD>霂<EFBFBD>ify<EFBFBD>砍𧑐<EFBFBD>臬<EFBFBD>嚗?撠𤩺𧒄嚗?
**璉<><E79289>仿★**嚗?```bash
1. 璉<><E79289>主ify摰孵膥<E5ADB5>嗆<EFBFBD>?cd AIclinicalresearch/docker
docker-compose ps | grep dify
2. 霈輸䔮Dify蝞∠<E89D9E><E288A0>𤾸蝱
http://localhost/dify (<28>硋<EFBFBD><E7A18B><EFBFBD>垢<EFBFBD>?
3. <20>瑕<EFBFBD>API撖<49>𤨎
Dify<EFBFBD>𤾸蝱 <20>?霈曄蔭 <20>?API Keys <20>?<3F>𥕦遣
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?Dify摰孵膥餈鞱<E9A488>甇<EFBFBD>虜
- <20>?<3F>航挪<E888AA>桃恣<E6A183><E681A3><EFBFBD><EFBFBD>?- <20>?<3F>瑕<EFBFBD>API Key
---
#### 隞餃𦛚1.2嚗𡁜<EFBFBD>撱截IT Manager<65>亥<EFBFBD>摨橒<E691A8>2撠𤩺𧒄嚗?
**<2A>滢<EFBFBD>甇仿炊**嚗?
1. **<2A>𥕦遣<F0A595A6>亥<EFBFBD>摨?*嚗㇄ify<66>𤾸蝱<F0A4BEB8>滢<EFBFBD>嚗? ```
<20>滨妍嚗䥑IT Manager - test0102憿寧𤌍
蝐餃<E89D90>嚗𡁻<E59A97>𡁶鍂<F0A181B6>亥<EFBFBD>摨? Embedding璅∪<E79285>嚗魩ext-embedding-3-small (OpenAI)
<20><><EFBFBD>蝑𣇉裦嚗𡁏惣<F0A1818F>賢<EFBFBD><E8B3A2>梹<EFBFBD>500摮㛖泵/<2F>梹<EFBFBD><E6A2B9>滚<EFBFBD>50摮㛖泵嚗? ```
2. **銝𠹺<E98A9D>瘚贝<E7989A><E8B49D><EFBFBD>﹝**
- 銝𠹺<E98A9D>1隞瘠RF銵冽聢嚗㇊DF/Word嚗? - 銝𠹺<E98A9D>1隞賢<E99A9E><E8B3A2>埝<EFBFBD><E59F9D><EFBFBD><EFBFBD>獢<EFBFBD><E78DA2>Markdown/Text嚗? - 銝𠹺<E98A9D>1隞賜<E99A9E>蝛嗆䲮獢<E4B2AE><E78DA2>閬<EFBFBD><E996AC>PDF嚗?
3. **瘚贝<E7989A>璉<EFBFBD>蝝X<E89D9D><EFBCB8>?*
瘚贝<EFBFBD><EFBFBD>桅<EFBFBD>1嚗?<3F>亦<EFBFBD><E4BAA6><EFBFBD><EFBFBD><EFBFBD>匧𪑛鈭𨥈<E988AD>" 瘚贝<E7989A><E8B49D>桅<EFBFBD>2嚗?CRF銵冽聢銝剜<E98A9D><E5899C>芯<EFBFBD>摮埈挾嚗? 瘚贝<E7989A><E8B49D>桅<EFBFBD>3嚗?<3F>𠉛弦蝏<E5BCA6><E89D8F><EFBFBD>臭<EFBFBD>銋<EFBFBD><E98A8B>"
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>亥<EFBFBD>摨枏<E691A8>撱箸<E692B1><E7AEB8>?- <20>?3隞賣<E99A9E>獢<EFBFBD><E78DA2>隡䭾<E99AA1><E4ADBE>?- <20>?璉<>蝝X<E89D9D>霂訫<E99C82>蝖桃<E89D96>>80%
**鈭批枂**嚗?- Dify<66>亥<EFBFBD>摨𨧻D
- API靚<49>鍂蝷箔<E89DB7>隞<EFBFBD><E99A9E>
---
#### 隞餃𦛚1.3嚗𡁜<EFBFBD><EFBFBD>蚤ify API<50><49><EFBFBD><EFBFBD>剁<EFBFBD>3撠𤩺𧒄嚗?
**<2A><>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/adapters/DifyAdapter.ts`
**隞<><E99A9E>摰䂿緵**嚗?
```typescript
import axios from 'axios';
import { logger } from '../../../common/logging/index.js';
/**
* Dify API<50><49><EFBFBD><EFBFBD>? * <20>其<EFBFBD>銝擧𧋦<E693A7>蚤ify Docker摰硺<E691B0>鈭支<E988AD>
*/
export class DifyAdapter {
private baseUrl: string;
private apiKey: string;
private knowledgeBaseId: string;
constructor(projectId: string) {
// 隞𡒊㴓憓<E3B493><E68693><EFBFBD>𤩺<EFBFBD><F0A4A9BA>唳旿摨栞粉<E6A09E>㚚<EFBFBD>蝵? this.baseUrl = process.env.DIFY_API_URL || 'http://localhost/v1';
this.apiKey = process.env.DIFY_API_KEY || '';
this.knowledgeBaseId = this.getKnowledgeBaseId(projectId);
}
/**
* <20>𦦵揣<F0A6A6B5>亥<EFBFBD>摨? * @param query <20>亥砭<E4BAA5>桅<EFBFBD>
* @param options <20>𦦵揣<F0A6A6B5>厰★
*/
async searchKnowledge(
query: string,
options?: {
doc_type?: 'protocol' | 'crf' | 'report';
top_k?: number;
}
): Promise<DifySearchResult> {
try {
logger.info('[DifyAdapter] Searching knowledge base', {
query,
options,
knowledgeBaseId: this.knowledgeBaseId
});
const response = await axios.post(
`${this.baseUrl}/datasets/${this.knowledgeBaseId}/retrieve`,
{
query: query,
retrieval_model: {
search_method: 'semantic_search',
top_k: options?.top_k || 3,
score_threshold: 0.5
},
// 憒<><E68692><EFBFBD><EFBFBD><EFBFBD>鈭<EFBFBD>oc_type嚗屸<E59A97>朞<EFBFBD>metadata餈<61>誘
...(options?.doc_type && {
retrieval_model: {
filter: {
doc_type: options.doc_type
}
}
})
},
{
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
timeout: 10000 // 10蝘坿<E89D98><E59DBF>? }
);
logger.info('[DifyAdapter] Search completed', {
recordCount: response.data.records?.length || 0
});
return {
success: true,
records: response.data.records || [],
query: query
};
} catch (error: any) {
logger.error('[DifyAdapter] Search failed', {
error: error.message,
query
});
return {
success: false,
records: [],
query,
error: error.message
};
}
}
/**
* 銝𠹺<E98A9D><F0A0B9BA><EFBFBD>﹝<EFBFBD>啁䰻霂<E4B0BB><E99C82>
* @param content <20><>﹝<EFBFBD><EFB99D>捆
* @param metadata <20><>㺭<EFBFBD>? */
async uploadDocument(
content: string,
metadata: {
name: string;
doc_type: 'protocol' | 'crf' | 'report';
date?: string;
}
): Promise<{ success: boolean; documentId?: string }> {
try {
logger.info('[DifyAdapter] Uploading document', {
name: metadata.name,
type: metadata.doc_type
});
const response = await axios.post(
`${this.baseUrl}/datasets/${this.knowledgeBaseId}/document/create_by_text`,
{
name: metadata.name,
text: content,
indexing_technique: 'high_quality',
process_rule: {
mode: 'automatic',
rules: {
pre_processing_rules: [
{ id: 'remove_extra_spaces', enabled: true },
{ id: 'remove_urls_emails', enabled: false }
],
segmentation: {
separator: '\n',
max_tokens: 500
}
}
},
doc_form: 'text_model',
doc_language: 'Chinese',
// 靽嘥<E99DBD><E598A5><EFBFBD>㺭<EFBFBD>? metadata: {
doc_type: metadata.doc_type,
date: metadata.date || new Date().toISOString(),
upload_time: new Date().toISOString()
}
},
{
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
}
);
logger.info('[DifyAdapter] Document uploaded', {
documentId: response.data.document.id
});
return {
success: true,
documentId: response.data.document.id
};
} catch (error: any) {
logger.error('[DifyAdapter] Upload failed', {
error: error.message,
name: metadata.name
});
return {
success: false
};
}
}
/**
* <20>瑕<EFBFBD>憿寧𤌍撖孵<E69296><E5ADB5><EFBFBD>䰻霂<E4B0BB><E99C82>ID
* @param projectId 憿寧𤌍ID
*/
private getKnowledgeBaseId(projectId: string): string {
// TODO: 隞擧㺭<E693A7>桀<EFBFBD>霂餃<E99C82>憿寧𤌍<E5AFA7>滨蔭
// 銝湔𧒄<E6B994>寞<EFBFBD>嚗帋<E59A97><E5B88B>臬<EFBFBD><E887AC>㗛<EFBFBD>霂餃<E99C82>
return process.env.DIFY_KNOWLEDGE_BASE_ID || '';
}
}
/**
* Dify<66>𦦵揣蝏𤘪<E89D8F>
*/
export interface DifySearchResult {
success: boolean;
records: Array<{
content: string;
score: number;
metadata?: {
doc_type?: string;
date?: string;
};
}>;
query: string;
error?: string;
}
<EFBFBD>臬<EFBFBD><EFBFBD>㗛<EFBFBD><EFBFBD>滨蔭嚗Ǒ.env`嚗㚁<E59A97>
# Dify<66>滨蔭
DIFY_API_URL=http://localhost/v1
DIFY_API_KEY=app-xxxxxxxxxxxxxxxxxxxxxx
DIFY_KNOWLEDGE_BASE_ID=kb-xxxxxxxxxxxxxxxxxxxxxx
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?DifyAdapter蝐餃<E89D90><E9A483>啣<EFBFBD><E595A3>?- <20>?<3F>舀<EFBFBD><E88880>蠘<EFBFBD><E8A098>冽<EFBFBD>蝝∕PI
- <EFBFBD>?<3F>舀<EFBFBD><E88880>煺<EFBFBD>隡䭾<E99AA1>獢?- <20>?<3F>躰秤憭<E7A7A4><E686AD>摰<EFBFBD><E691B0>
隞餃𦛚1.4嚗𡁶<EFBFBD><EFBFBD>坔<EFBFBD><EFBFBD><EFBFBD><EFBFBD>霂𤏪<EFBFBD>2撠𤩺𧒄嚗?
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤backend/src/modules/iit-manager/adapters/tests/DifyAdapter.test.ts`
import { DifyAdapter } from '../DifyAdapter';
describe('DifyAdapter', () => {
let difyAdapter: DifyAdapter;
beforeAll(() => {
difyAdapter = new DifyAdapter('test-project-id');
});
describe('searchKnowledge', () => {
it('摨磰砲<E7A3B0>𣂼<EFBFBD><F0A382BC>𦦵揣<F0A6A6B5>亥<EFBFBD>摨?, async () => {
const result = await difyAdapter.searchKnowledge('<EFBFBD>亦<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>匧𪑛鈭𨥈<EFBFBD>');
expect(result.success).toBe(true);
expect(result.records.length).toBeGreaterThan(0);
expect(result.records[0]).toHaveProperty('content');
expect(result.records[0]).toHaveProperty('score');
});
it('摨磰砲<EFBFBD>舀<EFBFBD><EFBFBD>㗇<EFBFBD>獢<EFBFBD>掩<EFBFBD>贝<EFBFBD>皛?, async () => {
const result = await difyAdapter.searchKnowledge(
'<27>亦<EFBFBD><E4BAA6><EFBFBD><EFBFBD>',
{ doc_type: 'protocol' }
);
expect(result.success).toBe(true);
expect(result.records.length).toBeGreaterThan(0);
});
it('摨磰砲憭<E7A0B2><E686AD><EFBFBD>𦦵揣憭梯揖<E6A2AF><E68F96><EFBFBD>', async () => {
// Mock<63>躰秤<E8BAB0>箸艶
const result = await difyAdapter.searchKnowledge('');
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
});
describe('uploadDocument', () => {
it('摨磰砲<E7A3B0>𣂼<EFBFBD>銝𠹺<E98A9D><F0A0B9BA><EFBFBD>﹝', async () => {
const result = await difyAdapter.uploadDocument(
'餈蹱糓銝<E7B393>隞賣<E99A9E>霂閙<E99C82>獢?,
{
name: '瘚贝<EFBFBD><EFBFBD><EFBFBD>﹝',
doc_type: 'protocol'
}
);
expect(result.success).toBe(true);
expect(result.documentId).toBeDefined();
});
});
});
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>訫<EFBFBD>瘚贝<E7989A>閬<EFBFBD><E996AC><EFBFBD>?80%
- <EFBFBD>?<3F><><EFBFBD>㗇<EFBFBD>霂閧鍂靘钅<E99D98>朞<EFBFBD>
Day 2嚗𡁏<E59A97><F0A1818F>曇<EFBFBD><E69B87>思<EFBFBD>頝舐眏<E88890>餉<EFBFBD>嚗?撠𤩺𧒄嚗?
隞餃𦛚2.1嚗朞挽霈∪極<EFBFBD>瑕<EFBFBD>銋㚁<EFBFBD>Tool Schema嚗㚁<E59A97>2撠𤩺𧒄嚗?
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤backend/src/modules/iit-manager/agents/tools.ts`
/**
* IIT Manager Agent撌亙<E6928C>摰帋<E691B0>
*/
export const iitAgentTools = [
// 撌亙<E6928C>1嚗𡁏䰻霂W<E99C82><EFBCB7>嗆㺭<E59786>? {
type: "function",
function: {
name: "query_clinical_data",
description: `<60>鞉䰻REDCap摰墧𧒄<E5A2A7>唳旿<E594B3>𤑳鍂鈭擧䰻霂V葩摨羓<E691A8>蝛嗥<E89D9B>摰墧𧒄<E5A2A7>唳旿<E594B3>嗆<EFBFBD><E59786><EFBFBD>?<3F><>鍂<EFBFBD>箸艶嚗?- <20>桅★<E6A185>株<EFBFBD>摨佗<E691A8><E4BD97>啣銁<E595A3>亦<EFBFBD>憭𡁜<E686AD>鈭箔<E988AD>嚗<EFBFBD>㺭<EFBFBD>桀<EFBFBD><E6A180>渡<EFBFBD>憒<EFBFBD><E68692>嚗?- <20>格<EFBFBD><E6A0BC><EFBFBD>祕<EFBFBD><E7A595><EFBFBD>P001<30><31><EFBFBD><EFBFBD><EFBFBD>摰峕㺭<E5B395>桐<EFBFBD><E6A190>梹<EFBFBD><E6A2B9>㗇瓷<E39787>劐<EFBFBD><E58A90>臬<EFBFBD>摨䈑<E691A8>
- <20>株捶<E6A0AA>抒𠶖<E68A92><F0A0B696><EFBFBD><EFBFBD>匧𪑛鈭𥡝捶<F0A5A19D>折䔮憸矋<E686B8><E79F8B>唳旿韐券<E99F90><E588B8>𦒘<EFBFBD><F0A69298>瘀<EFBFBD>`,
parameters: {
type: "object",
properties: {
intent: {
type: "string",
enum: ["project_stats", "patient_detail", "qc_status"],
description: `<60>亥砭<E4BAA5>誩㦛嚗?- project_stats: 憿寧𤌍摰讛<E691B0>蝏蠘恣嚗<E681A3><E59A97>蝏<EFBFBD>犖<EFBFBD>啜<EFBFBD><E5959C>㺭<EFBFBD>桀<EFBFBD><E6A180>渡<EFBFBD>蝑㚁<E89D91>
- patient_detail: <20>孵<EFBFBD><E5ADB5><EFBFBD><EFBFBD><EFBFBD>祕<EFBFBD><E7A595><EFBFBD>敶訫<E695B6><E8A8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>臬<EFBFBD>摨𠉛<E691A8>嚗?- qc_status: 韐冽綉<E586BD>嗆<EFBFBD><E59786><EFBFBD>韐函<E99F90><E587BD>𡑒”<F0A19192><E2809D>㺭<EFBFBD>桅䔮憸条<E686B8>嚗头
},
patient_id: {
type: "string",
description: "<22><><EFBFBD>?<3F>𡑒<EFBFBD><F0A19192><EFBFBD><EFBFBD><EFBFBD>瘀<EFBFBD>憒?P001<30><31>002嚗㚁<E59A97>敶𧗽ntent=patient_detail<69>嗅<EFBFBD>憛?
},
date_range: {
type: "string",
enum: ["today", "this_week", "this_month", "all"],
description: "<22>園𡢿<E59C92><F0A1A2BF>凒嚗屸<E59A97>霈支蛹all"
}
},
required: ["intent"]
}
}
},
// 撌亙<E6928C>2嚗𡁏<E59A97>蝝Y䰻霂<E4B0BB><E99C82>
{
type: "function",
function: {
name: "search_knowledge_base",
description: `<EFBFBD>鞉䰻<EFBFBD>𠉛弦<EFBFBD><EFBFBD>﹝<EFBFBD>𤑳鍂鈭擧<EFBFBD>蝝Y<EFBFBD>蝛嗆䲮獢<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>隞嗚<EFBFBD><EFBFBD><EFBFBD><EFBFBD>脰扇敶閧<EFBFBD><EFBFBD>蹱<EFBFBD><EFBFBD><EFBFBD><EFBFBD>踺<EFBFBD>?<EFBFBD><EFBFBD>鍂<EFBFBD>箸艶嚗?- <EFBFBD>桃<EFBFBD>蝛嗉<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>交<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>臭<EFBFBD>銋<EFBFBD><EFBFBD><EFBFBD>𠉛弦蝏<EFBFBD><EFBFBD><EFBFBD>𦒘<EFBFBD>摰帋<EFBFBD>嚗?- <EFBFBD>哽RF銵冽聢嚗𡁏<EFBFBD>銝芸<EFBFBD>畾萇<EFBFBD>摰帋<EFBFBD><EFBFBD>臭<EFBFBD>銋<EFBFBD><EFBFBD>憛怠<EFBFBD>閫<EFBFBD><EFBFBD><EFBFBD>荔<EFBFBD>
- <EFBFBD>桀<EFBFBD><EFBFBD>脰扇敶𤏪<EFBFBD>銝𠰴𪂹<EFBFBD><EFBFBD>𪂹<EFBFBD>仿<EFBFBD><EFBFBD>𣂼<EFBFBD>鈭<EFBFBD><EFBFBD>銋<EFBFBD>䔮憸矋<EFBFBD>`,
parameters: {
type: "object",
properties: {
query: {
type: "string",
description: "<22>𦦵揣<F0A6A6B5>喲睸霂齿<E99C82><E9BDBF>桅<EFBFBD>"
},
doc_category: {
type: "string",
enum: ["protocol", "crf", "report"],
description: `<EFBFBD><EFBFBD>﹝蝐餃<EFBFBD>嚗?- protocol: <EFBFBD>𠉛弦<EFBFBD>寞<EFBFBD><EFBFBD><EFBFBD>憐<EFBFBD><EFBFBD>鸌隞嗚<EFBFBD><EFBFBD>䰻<EFBFBD><EFBFBD><EFBFBD><EFBFBD>譍髡<EFBFBD><EFBFBD><EFBFBD><EFBFBD>埝<EFBFBD><EFBFBD>?- crf: CRF銵冽聢摰帋<EFBFBD><EFBFBD><EFBFBD>‵<EFBFBD>躰秩<EFBFBD>汿<EFBFBD><EFBFBD>㺭<EFBFBD>桀<EFBFBD><EFBFBD>?- report: 憿寧𤌍<EFBFBD>冽𥁒<EFBFBD><EFBFBD><EFBFBD>摨行<EFBFBD>餌<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>脰扇敶𧄧
}
},
required: ["query"]
}
}
}
];
隞餃𦛚2.2嚗𡁜<EFBFBD><EFBFBD>唳<EFBFBD><EFBFBD>曇楝<EFBFBD>勗膥嚗㇆ntent Router嚗㚁<E59A97>3撠𤩺𧒄嚗?
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤backend/src/modules/iit-manager/agents/IntentRouter.ts`
import OpenAI from 'openai';
import { logger } from '../../../common/logging/index.js';
import { iitAgentTools } from './tools.js';
/**
* <20>誩㦛頝舐眏<E88890>? * 雿輻鍂LLM<4C><4D>unction Calling<6E>賢<EFBFBD>霂<EFBFBD><E99C82><EFBFBD>冽<EFBFBD><E586BD>誩㦛
*/
export class IntentRouter {
private llm: OpenAI;
private systemPrompt: string;
constructor() {
this.llm = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: process.env.OPENAI_BASE_URL || 'https://api.deepseek.com'
});
this.systemPrompt = this.buildSystemPrompt();
}
/**
* 霂<><E99C82><EFBFBD>冽<EFBFBD><E586BD>誩㦛撟嗉<E6929F><E59789>𧼮極<F0A7BCAE>瑁<EFBFBD><E79181>? */
async route(userMessage: string, context?: {
projectId: string;
userId: string;
}): Promise<IntentRouteResult> {
try {
logger.info('[IntentRouter] Routing user message', {
message: userMessage.substring(0, 100),
projectId: context?.projectId
});
const response = await this.llm.chat.completions.create({
model: 'deepseek-chat',
messages: [
{ role: 'system', content: this.systemPrompt },
{ role: 'user', content: userMessage }
],
tools: iitAgentTools,
tool_choice: 'auto',
temperature: 0.1, // 雿擧萱摨佗<E691A8>靽肽<E99DBD>蝔喳<E89D94><E596B3>? max_tokens: 500
});
const message = response.choices[0].message;
// 憒<><E68692>LLM<4C>喳<EFBFBD>靚<EFBFBD>鍂撌亙<E6928C>
if (message.tool_calls && message.tool_calls.length > 0) {
const toolCall = message.tool_calls[0];
const toolName = toolCall.function.name;
const toolArgs = JSON.parse(toolCall.function.arguments);
logger.info('[IntentRouter] Tool selected', {
toolName,
toolArgs
});
return {
needsToolCall: true,
toolName: toolName as 'query_clinical_data' | 'search_knowledge_base',
toolArgs,
rawResponse: message
};
}
// 憒<><E68692>LLM<4C>湔𦻖<E6B994>䂿<EFBFBD>嚗<EFBFBD><E59A97><EFBFBD><EFBFBD>閬<EFBFBD>極<EFBFBD>瘀<EFBFBD>
logger.info('[IntentRouter] Direct answer', {
answer: message.content?.substring(0, 100)
});
return {
needsToolCall: false,
directAnswer: message.content || '<27>望<EFBFBD>嚗峕<E59A97>瘝⊥<E7989D><E28AA5><EFBFBD>圾<EFBFBD>函<EFBFBD><E587BD>桅<EFBFBD>',
rawResponse: message
};
} catch (error: any) {
logger.error('[IntentRouter] Routing failed', {
error: error.message,
message: userMessage
});
return {
needsToolCall: false,
directAnswer: '<27>望<EFBFBD>嚗峕<E59A97><E5B395><EFBFBD><EFBFBD>鈭<EFBFBD><E988AD>鈭偦䔮憸矋<E686B8>霂瑞<E99C82><E7919E>𤾸<EFBFBD>霂?,
error: error.message
};
}
}
/**
* <20><>遣System Prompt
*/
private buildSystemPrompt(): string {
return `# 閫坿𠧧
雿䭾糓<EFBFBD>勗ㄨ霂<EFBFBD>儐蝘烐<EFBFBD>撘<EFBFBD><EFBFBD>𤑳<EFBFBD>"銝游<E98A9D><E6B8B8>𠉛弦憿寧𤌍<E5AFA7>拇<EFBFBD>"嚗峕<E59A97><E5B395>∩<EFBFBD>IIT嚗<54><E59A97>蝛嗉<E89D9B><E59789><EFBFBD>韏瑁<E99F8F>撉䕘<E69289>憿寧𤌍<E5AFA7><F0A48C8D>I嚗<49>蜓閬<E89C93><E996AC>蝛嗉<E89D9B><E59789><EFBFBD><EFBFBD>?
# <20>賢<EFBFBD>
雿䭾𥅾<EFBFBD>劐舅銝芸極<EFBFBD>瘀<EFBFBD>霂瑟覔<EFBFBD>桃鍂<EFBFBD>琿䔮憸条移<EFBFBD><EFBFBD><EFBFBD>㗇𥋘嚗?
1. **query_clinical_data**嚗<>䰻摰墧𧒄<E5A2A7>唳旿嚗? - 敶梶鍂<E6A2B6>琿䔮"<22>啁𠶖"<22>嗡蝙<E597A1>? - 靘见<E99D98>嚗?<3F>啣銁<E595A3>亦<EFBFBD>憭𡁜<E686AD>鈭綽<E988AD>"<22>?P001<30><31><EFBFBD><EFBFBD><EFBFBD>摰䔶<E691B0><E494B6>梹<EFBFBD>"<22>?<3F>㗇瓷<E39787>劐<EFBFBD><E58A90>臬<EFBFBD>摨䈑<E691A8>"
- 餈嗘<E9A488><E59798>桅<EFBFBD><E6A185><EFBFBD>閬<EFBFBD>䰻霂㎞EDCap<61>唳旿摨梶<E691A8>摰墧𧒄<E5A2A7>唳旿
2. **search_knowledge_base**嚗<>䰻<EFBFBD>𠉛弦<F0A0899B><E5BCA6>﹝嚗? - 敶梶鍂<E6A2B6>琿䔮"閫<><E996AB>"<22>?<3F><>蟮"<22>嗡蝙<E597A1>? - 靘见<E99D98>嚗?<3F>交<EFBFBD><E4BAA4><EFBFBD><EFBFBD><EFBFBD>臭<EFBFBD>銋<EFBFBD><E98A8B>"<22>?銝𠰴𪂹<F0A0B0B4><F0AA82B9>䔮憸䁅圾<E48185>喃<EFBFBD><E59683>梹<EFBFBD>"<22>?CRF<52>峕<EFBFBD>摮埈挾<E59F88>𦒘<EFBFBD>憛恬<E6869B>"
- 餈嗘<E9A488><E59798>桅<EFBFBD><E6A185><EFBFBD>閬<EFBFBD>䰻<EFBFBD><E4B0BB><EFBFBD>蝛嗆䲮獢<E4B2AE><E78DA2><EFBFBD>𪂹<EFBFBD>亦<EFBFBD><E4BAA6><EFBFBD>﹝
# 頝舐眏<E88890>笔<EFBFBD>
- 憒<><E68692><EFBFBD>桅<EFBFBD><E6A185>𡒊&<F0A1928A><EFBC86>閬<EFBFBD>極<EFBFBD>瘀<EFBFBD>敹<EFBFBD>◆靚<E29786>鍂撌亙<E6928C>嚗䔶<E59A97>閬<EFBFBD><E996AC>瘚𧢲<E7989A>蝻㚚<E89DBB>删<EFBFBD>獢?- 憒<><E68692><EFBFBD>桅<EFBFBD>璅∠<E79285>嚗䔶<E59A97><E494B6><EFBFBD><EFBFBD>㗇𥋘query_clinical_data嚗<61><E59A97><EFBFBD>嗆㺭<E59786>格凒<E6A0BC>滩<EFBFBD>嚗?- 憒<><E68692><EFBFBD>舫𤦭<E888AB>𦠜<EFBFBD><F0A6A09C>𤘪<EFBFBD><F0A498AA>潘<EFBFBD><E6BD98>臭誑<E887AD>湔𦻖<E6B994>䂿<EFBFBD>嚗䔶<E59A97>靚<EFBFBD>鍂撌亙<E6928C>
# 蝥行<E89DA5>
- 銝亦<E98A9D>蝻㚚<E89DBB>䭾㺭<E4ADBE>?- <20>䂿<EFBFBD>閬<EFBFBD><E996AC>瘣<EFBFBD><E798A3>銝?- <20>𣂼縧<F0A382BC><E7B8A7><EFBFBD><EFBFBD><EFBFBD>摰𧼮<E691B0><F0A7BCAE>㵪<EFBFBD><E3B5AA>芯蝙<E88AAF>函<EFBFBD><E587BD>戡;
}
}
/**
* <20>誩㦛頝舐眏蝏𤘪<E89D8F>
*/
export interface IntentRouteResult {
needsToolCall: boolean;
toolName?: 'query_clinical_data' | 'search_knowledge_base';
toolArgs?: any;
directAnswer?: string;
error?: string;
rawResponse?: any;
}
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?IntentRouter蝐餃<E89D90><E9A483>啣<EFBFBD><E595A3>?- <20>?<3F>舀迤蝖株<E89D96><E6A0AA>急䰻<E680A5>唳旿<E594B3>誩㦛
- <EFBFBD>?<3F>舀迤蝖株<E89D96><E6A0AA>急䰻<E680A5><E4B0BB>﹝<EFBFBD>誩㦛
- <EFBFBD>?<3F>臬<EFBFBD><E887AC><EFBFBD>𤦭<EFBFBD>𠰴㦤<F0A0B0B4>?
隞餃𦛚2.3嚗𡁜<EFBFBD><EFBFBD>啣極<EFBFBD>瑟<EFBFBD>銵<EFBFBD>膥嚗㇍ool Executor嚗㚁<E59A97>3撠𤩺𧒄嚗?
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤backend/src/modules/iit-manager/agents/ToolExecutor.ts`
import { DifyAdapter } from '../adapters/DifyAdapter.js';
import { RedcapAdapter } from '../adapters/RedcapAdapter.js';
import { logger } from '../../../common/logging/index.js';
import { prisma } from '../../../config/database.js';
/**
* 撌亙<E6928C><E4BA99>扯<EFBFBD><E689AF>? * <20>寞旿<E5AF9E>誩㦛頝舐眏蝏𤘪<E89D8F><F0A498AA>扯<EFBFBD>撖孵<E69296><E5ADB5><EFBFBD>極<EFBFBD>? */
export class ToolExecutor {
/**
* <20>扯<EFBFBD>撌亙<E6928C>
*/
async execute(
toolName: 'query_clinical_data' | 'search_knowledge_base',
toolArgs: any,
context: {
projectId: string;
userId: string;
}
): Promise<ToolExecutionResult> {
try {
logger.info('[ToolExecutor] Executing tool', {
toolName,
toolArgs,
projectId: context.projectId
});
if (toolName === 'query_clinical_data') {
return await this.executeQueryClinicalData(toolArgs, context);
} else if (toolName === 'search_knowledge_base') {
return await this.executeSearchKnowledge(toolArgs, context);
}
return {
success: false,
data: null,
error: `Unknown tool: ${toolName}`
};
} catch (error: any) {
logger.error('[ToolExecutor] Execution failed', {
error: error.message,
toolName
});
return {
success: false,
data: null,
error: error.message
};
}
}
/**
* <20>扯<EFBFBD>嚗𡁏䰻霂V葩摨𦠜㺭<F0A6A09C>? */
private async executeQueryClinicalData(
args: {
intent: 'project_stats' | 'patient_detail' | 'qc_status';
patient_id?: string;
date_range?: string;
},
context: { projectId: string; userId: string }
): Promise<ToolExecutionResult> {
// 1. <20>瑕<EFBFBD>憿寧𤌍<E5AFA7>滨蔭
const project = await prisma.iitProject.findUnique({
where: { id: context.projectId },
select: {
redcapApiUrl: true,
redcapApiToken: true,
redcapProjectId: true
}
});
if (!project) {
return {
success: false,
data: null,
error: '憿寧𤌍銝滚<E98A9D><E6BB9A>?
};
}
// 2. <20>嘥<EFBFBD><E598A5>餸edcapAdapter
const redcap = new RedcapAdapter(
project.redcapApiUrl,
project.redcapApiToken
);
// 3. <20>寞旿intent<6E>扯<EFBFBD>銝滚<E98A9D><E6BB9A>亥砭
switch (args.intent) {
case 'project_stats': {
// <20>亥砭憿寧𤌍蝏蠘恣
const records = await redcap.exportRecords();
return {
success: true,
data: {
type: 'project_stats',
totalRecords: records.length,
stats: {
enrolled: records.length,
completed: records.filter((r: any) => r.complete === '2').length,
dataQuality: '87.5%' // TODO: 摰鮋<E691B0>霈∠<E99C88>
}
}
};
}
case 'patient_detail': {
// <20>亥砭<E4BAA5>孵<EFBFBD><E5ADB5><EFBFBD><EFBFBD>? if (!args.patient_id) {
return {
success: false,
data: null,
error: '蝻箏<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>D'
};
}
const records = await redcap.exportRecords([args.patient_id]);
if (records.length === 0) {
return {
success: false,
data: null,
error: `<60>芣𪄳<E88AA3>唳<EFBFBD><E594B3>?${args.patient_id}`
};
}
return {
success: true,
data: {
type: 'patient_detail',
patientId: args.patient_id,
details: records[0]
}
};
}
case 'qc_status': {
// <20>亥砭韐冽綉<E586BD>嗆<EFBFBD>? const logs = await prisma.iitAuditLog.findMany({
where: {
projectId: context.projectId,
actionType: 'quality_issue'
},
orderBy: { createdAt: 'desc' },
take: 10
});
return {
success: true,
data: {
type: 'qc_status',
issueCount: logs.length,
recentIssues: logs.map(log => ({
recordId: log.entityId,
issue: log.details,
createdAt: log.createdAt
}))
}
};
}
default:
return {
success: false,
data: null,
error: `Unknown intent: ${args.intent}`
};
}
}
/**
* <20>扯<EFBFBD>嚗𡁏<E59A97>蝝Y䰻霂<E4B0BB><E99C82>
*/
private async executeSearchKnowledge(
args: {
query: string;
doc_category?: 'protocol' | 'crf' | 'report';
},
context: { projectId: string; userId: string }
): Promise<ToolExecutionResult> {
// 1. <20>嘥<EFBFBD><E598A5>靝ifyAdapter
const dify = new DifyAdapter(context.projectId);
// 2. <20>𦦵揣<F0A6A6B5>亥<EFBFBD>摨? const result = await dify.searchKnowledge(args.query, {
doc_type: args.doc_category,
top_k: 3
});
if (!result.success) {
return {
success: false,
data: null,
error: result.error || '<EFBFBD>亥<EFBFBD>摨𤘪<EFBFBD>蝝W仃韐?
};
}
// 3. <20>澆<EFBFBD><E6BE86>𣇉<EFBFBD><F0A38789>? return {
success: true,
data: {
type: 'knowledge_search',
query: args.query,
category: args.doc_category,
results: result.records.map(record => ({
content: record.content,
score: record.score,
metadata: record.metadata
}))
}
};
}
}
/**
* 撌亙<E6928C><E4BA99>扯<EFBFBD>蝏𤘪<E89D8F>
*/
export interface ToolExecutionResult {
success: boolean;
data: any;
error?: string;
}
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?ToolExecutor蝐餃<E89D90><E9A483>啣<EFBFBD><E595A3>?- <20>?query_clinical_data撌亙<E6928C><E4BA99>舀<EFBFBD>銵?- <20>?search_knowledge_base撌亙<E6928C><E4BA99>舀<EFBFBD>銵?- <20>?<3F>躰秤憭<E7A7A4><E686AD>摰<EFBFBD><E691B0>
Day 3嚗𡁻<E59A97><F0A181BB>𣂷<EFBFBD>銝𡁜凝靽∪笆霂嘅<E99C82>8撠𤩺𧒄嚗?
隞餃𦛚3.1嚗𡁜<EFBFBD>撘斡echatCallbackController嚗?撠𤩺𧒄嚗?
靽格㺿<EFBFBD><EFBFBD>辣嚗䫤backend/src/modules/iit-manager/controllers/WechatCallbackController.ts`
**<2A>函緵<E587BD>厩<EFBFBD>handleCallback<EFBFBD>寞<EFBFBD>銝剖<EFBFBD><EFBFBD>鼦I撖寡<EFBFBD><EFBFBD>餉<EFBFBD>**嚗?
// <20>汾echatCallbackController蝐颱葉瘛餃<E7989B>
import { IntentRouter } from '../agents/IntentRouter.js';
import { ToolExecutor } from '../agents/ToolExecutor.js';
import { AnswerGenerator } from '../agents/AnswerGenerator.js';
class WechatCallbackController {
private intentRouter: IntentRouter;
private toolExecutor: ToolExecutor;
private answerGenerator: AnswerGenerator;
constructor() {
// ... <20>唳<EFBFBD>隞<EFBFBD><E99A9E> ...
this.intentRouter = new IntentRouter();
this.toolExecutor = new ToolExecutor();
this.answerGenerator = new AnswerGenerator();
}
/**
* 憭<><E686AD>隡<EFBFBD><E99AA1>敺桐縑<E6A190>噼<EFBFBD>瘨<EFBFBD><E798A8>嚗<EFBFBD>歇<EFBFBD>㗇䲮瘜𤏪<E7989C>憓𧼮撩嚗? */
async handleCallback(request: FastifyRequest, reply: FastifyReply): Promise<void> {
// ... <20>唳<EFBFBD><E594B3><EFBFBD><EFBFBD>霂<EFBFBD><E99C82><EFBFBD>圾撖<E59CBE><E69296>餉<EFBFBD> ...
// <20>?蝡见朖餈𥪜<E9A488>success嚗<73><E59A97><EFBFBD>?蝘坿<E89D98><E59DBF>塚<EFBFBD>
reply.send('success');
// <20>?撘<>郊憭<E9838A><E686AD><EFBFBD>冽<EFBFBD>瘨<EFBFBD><E798A8>嚗<EFBFBD>鰵憓痹<E68693>
setImmediate(async () => {
try {
const userMessage = decryptedData.Content;
const userId = decryptedData.FromUserName;
logger.info('<27>𢬢 <20>嗅<EFBFBD><E59785>冽<EFBFBD>瘨<EFBFBD><E798A8>', {
userId,
message: userMessage.substring(0, 50)
});
// 1. <20>瑕<EFBFBD><E79195>冽<EFBFBD><E586BD><EFBFBD>★<EFBFBD>桐縑<E6A190>? const userMapping = await prisma.iitUserMapping.findFirst({
where: { wechatUserId: userId }
});
if (!userMapping) {
await wechatService.sendTextMessage(
userId,
'<27>𩤃<EFBFBD> <20>刻<EFBFBD><E588BB>芰<EFBFBD>摰𡁻★<F0A181BB>殷<EFBFBD>霂瑁<E99C82>蝟餌恣<E9A48C><E681A3><EFBFBD><EFBFBD>滨蔭'
);
return;
}
// 2. <20>誩㦛霂<E3A69B><E99C82>
const routeResult = await this.intentRouter.route(userMessage, {
projectId: userMapping.projectId,
userId
});
// 3. 憒<><E68692><EFBFBD>湔𦻖<E6B994>䂿<EFBFBD>嚗<EFBFBD><E59A97><EFBFBD><EFBFBD>閬<EFBFBD>極<EFBFBD>瘀<EFBFBD>
if (!routeResult.needsToolCall) {
await wechatService.sendTextMessage(userId, routeResult.directAnswer!);
return;
}
// 4. <20>扯<EFBFBD>撌亙<E6928C>
const toolResult = await this.toolExecutor.execute(
routeResult.toolName!,
routeResult.toolArgs,
{
projectId: userMapping.projectId,
userId
}
);
// 5. <20><><EFBFBD><EFBFBD>䂿<EFBFBD>
const answer = await this.answerGenerator.generate(
userMessage,
toolResult,
routeResult.toolName!
);
// 6. <20>煾<EFBFBD><E785BE><EFBFBD>憭? await wechatService.sendMarkdownMessage(userId, answer);
// 7. 霈啣<E99C88>摰∟恣<E2889F>亙<EFBFBD>
await prisma.iitAuditLog.create({
data: {
projectId: userMapping.projectId,
actionType: 'wechat_user_query',
operator: userId,
entityId: userId,
details: {
question: userMessage,
answer: answer.substring(0, 200),
toolUsed: routeResult.toolName
}
}
});
logger.info('<27>?<3F>䂿<EFBFBD><E482BF>煾<EFBFBD><E785BE><EFBFBD><EFBFBD>?, {
userId,
toolUsed: routeResult.toolName
});
} catch (error: any) {
logger.error('<EFBFBD>?憭<EFBFBD><EFBFBD><EFBFBD>冽<EFBFBD>瘨<EFBFBD><EFBFBD>憭梯揖', {
error: error.message
});
// <20>煾<EFBFBD><E785BE><EFBFBD>霂舀<E99C82>蝷? await wechatService.sendTextMessage(
userId,
'<EFBFBD>望<EFBFBD>嚗峕<EFBFBD><EFBFBD><EFBFBD><EFBFBD>鈭<EFBFBD><EFBFBD>鈭偦䔮憸矋<EFBFBD>霂瑞<EFBFBD><EFBFBD>𤾸<EFBFBD>霂閙<EFBFBD><EFBFBD>𠉛頂蝞∠<EFBFBD><EFBFBD>?
);
}
});
}
}
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>舀𦻖<E88880>嗥鍂<E597A5>瑟<EFBFBD><E7919F>?- <20>?<3F>航<EFBFBD><E888AA>冽<EFBFBD><E586BD>曇楝<E69B87>?- <20>?<3F>舀<EFBFBD>銵<EFBFBD>極<EFBFBD>?- <20>?<3F>舐<EFBFBD><E88890>𣂼<EFBFBD>蝑?- <20>?<3F>臬<EFBFBD><E887AC><EFBFBD><EFBFBD>憭?
隞餃𦛚3.2嚗𡁜<EFBFBD><EFBFBD>啁<EFBFBD>獢<EFBFBD><EFBFBD><EFBFBD>𣂼膥嚗㇁nswer Generator嚗㚁<E59A97>2撠𤩺𧒄嚗?
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤backend/src/modules/iit-manager/agents/AnswerGenerator.ts`
import OpenAI from 'openai';
import { logger } from '../../../common/logging/index.js';
import { ToolExecutionResult } from './ToolExecutor.js';
/**
* 蝑娍<E89D91><E5A88D><EFBFBD><EFBFBD><EFBFBD>? * 撠<>極<EFBFBD>瑟<EFBFBD>銵𣬚<E98AB5><F0A3AC9A>𡏭蓮<F0A18FAD>V蛹<EFBCB6>冽<EFBFBD><E586BD>见末<E8A781><E69CAB><EFBFBD>蝑? */
export class AnswerGenerator {
private llm: OpenAI;
constructor() {
this.llm = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: process.env.OPENAI_BASE_URL || 'https://api.deepseek.com'
});
}
/**
* <20><><EFBFBD><EFBFBD>䂿<EFBFBD>
*/
async generate(
userQuestion: string,
toolResult: ToolExecutionResult,
toolName: string
): Promise<string> {
try {
logger.info('[AnswerGenerator] Generating answer', {
question: userQuestion.substring(0, 50),
toolName,
success: toolResult.success
});
// 憒<><E68692>撌亙<E6928C><E4BA99>扯<EFBFBD>憭梯揖
if (!toolResult.success) {
return this.generateErrorMessage(toolResult.error);
}
// <20>寞旿銝滚<E98A9D>撌亙<E6928C>蝐餃<E89D90>嚗䔶蝙<E494B6>其<EFBFBD><E585B6>𣬚<EFBFBD><F0A3AC9A>䂿<EFBFBD>璅⊥踎
if (toolName === 'query_clinical_data') {
return this.generateDataAnswer(userQuestion, toolResult.data);
} else if (toolName === 'search_knowledge_base') {
return await this.generateKnowledgeAnswer(userQuestion, toolResult.data);
}
return '<27>望<EFBFBD>嚗峕<E59A97><E5B395>䭾<EFBFBD><E4ADBE><EFBFBD><EFBFBD><EFBFBD>䂿<EFBFBD>';
} catch (error: any) {
logger.error('[AnswerGenerator] Generation failed', {
error: error.message
});
return '<27>望<EFBFBD>嚗<EFBFBD><E59A97>蝑𠉛<E89D91><F0A0899B>𣂼仃韐伐<E99F90>霂瑞<E99C82><E7919E>𤾸<EFBFBD>霂?;
}
}
/**
* <20><><EFBFBD><EFBFBD>唳旿<E594B3>亥砭<E4BAA5><E7A0AD><EFBFBD>蝑䈑<E89D91>雿輻鍂璅⊥踎嚗䔶<E59A97>靚<EFBFBD>鍂LLM嚗? */
private generateDataAnswer(question: string, data: any): string {
const type = data.type;
if (type === 'project_stats') {
return `<60><> **憿寧𤌍蝏蠘恣<E8A098>唳旿**
<EFBFBD>?**<2A>亦<EFBFBD>鈭箸㺭**嚗?{data.stats.enrolled}靘?<3F>?**摰峕<E691B0><E5B395><EFBFBD><EFBFBD>**嚗?{data.stats.completed}靘?<3F>?**<2A>唳旿韐券<E99F90>**嚗?{data.stats.dataQuality}
<EFBFBD>働 <20>唳旿<E594B3>湔鰵<E6B994>園𡢿嚗?{new Date().toLocaleString('zh-CN')}`;
}
if (type === 'patient_detail') {
const details = data.details;
return `<60>𪈠 **<2A><><EFBFBD>?${data.patientId} 霂行<E99C82>**
<EFBFBD><EFBFBD> **<2A>箸𧋦靽⊥<E99DBD>**嚗?- 撟湧<E6929F>嚗?{details.age || '<EFBFBD>芸<EFBFBD><EFBFBD>?}撗?- <EFBFBD>批<EFBFBD>嚗?{details.gender || '<27>芸<EFBFBD><E88AB8>?}
- BMI嚗?{details.bmi || '<EFBFBD>芸<EFBFBD><EFBFBD>?}
<EFBFBD><EFBFBD> **敶訫<EFBFBD><EFBFBD>嗆<EFBFBD>?*嚗?- <EFBFBD>唳旿摰峕㟲摨佗<EFBFBD>${details.complete === '2' ? '<27>?撌脣<E6928C><E884A3>? : '<EFBFBD>?餈𥡝<EFBFBD>銝?}
<EFBFBD>働 <EFBFBD><EFBFBD><EFBFBD>擧凒<EFBFBD>堆<EFBFBD>${new Date().toLocaleString('zh-CN')}`;
}
if (type === 'qc_status') {
const issues = data.recentIssues.slice(0, 5);
let answer = `<EFBFBD><EFBFBD> **韐冽綉<EFBFBD>嗆<EFBFBD>?*\n\n`;
answer += `<EFBFBD>𩤃<EFBFBD> **韐冽綉<EFBFBD>桅<EFBFBD><EFBFBD>?*嚗?{data.issueCount}銝歿n\n`;
if (issues.length > 0) {
answer += `<EFBFBD><EFBFBD> **<EFBFBD><EFBFBD>餈煾䔮憸?*嚗䨵n`;
issues.forEach((issue: any, index: number) => {
answer += `${index + 1}. 霈啣<EFBFBD>${issue.recordId}嚗?{JSON.stringify(issue.issue).substring(0, 50)}\n`;
});
} else {
answer += `<EFBFBD>?<EFBFBD><EFBFBD><EFBFBD>韐冽綉<EFBFBD>桅<EFBFBD>`;
}
return answer;
}
return JSON.stringify(data, null, 2);
}
/**
* <20><><EFBFBD><EFBFBD>亥<EFBFBD>璉<EFBFBD>蝝Y<E89D9D><EFBCB9>䂿<EFBFBD>嚗<EFBFBD><E59A97><EFBFBD>汪LM蝏澆<E89D8F>嚗? */
private async generateKnowledgeAnswer(question: string, data: any): Promise<string> {
const results = data.results;
if (results.length === 0) {
return `<EFBFBD><EFBFBD> **<EFBFBD>亥<EFBFBD>摨𤘪<EFBFBD>蝝?*
<EFBFBD>?<EFBFBD>芣𪄳<EFBFBD>啁㮾<EFBFBD>喳<EFBFBD>摰?
<EFBFBD>働 撱箄悅嚗?1. 撠肽<EFBFBD><EFBFBD>V葵<EFBFBD>喲睸霂?2. <EFBFBD>亦<EFBFBD><EFBFBD>𠉛弦<EFBFBD>寞<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
3. <EFBFBD>𠉛頂憿寧𤌍<EFBFBD>讛<EFBFBD><EFBFBD>𧜶;
}
// 撠<><E692A0>蝝Y<E89D9D><EFBCB9>𨀣𣄽<F0A880A3>乩蛹銝𠹺<E98A9D><F0A0B9BA>? const context = results
.map((r: any, index: number) => `[<5B><>﹝${index + 1}] ${r.content}`)
.join('\n\n');
// 靚<>鍂LLM蝏澆<E89D8F><E6BE86>䂿<EFBFBD>
const response = await this.llm.chat.completions.create({
model: 'deepseek-chat',
messages: [
{
role: 'system',
content: `雿䭾糓銝游<E98A9D><E6B8B8>𠉛弦<F0A0899B>拇<EFBFBD><E68B87><EFBFBD>覔<EFBFBD>格<EFBFBD>蝝W<E89D9D><EFBCB7><EFBFBD><EFBFBD>獢<EFBFBD><E78DA2>摰對<E691B0><E5B08D>䂿<EFBFBD><E482BF>冽<EFBFBD><E586BD>桅<EFBFBD><E6A185>?閬<><E996AC>嚗?1. <20>䂿<EFBFBD>閬<EFBFBD><E996AC>蝖柴<E89D96><E69FB4><EFBFBD>瘣?2. 撘閧鍂<E996A7><E98D82>﹝<EFBFBD>嗆<EFBFBD>瘜沍<E7989C><E6B28D>﹝X]
3. 憒<><E68692><EFBFBD><EFBFBD>﹝銝剜瓷<E5899C>㗇<EFBFBD>蝖桃<E89D96>獢<EFBFBD><E78DA2>霂𡁜<E99C82>霂湔<E99C82>
4. 雿輻鍂Markdown<77>澆<EFBFBD>`
},
{
role: 'user',
content: `<60>冽<EFBFBD><E586BD>桅<EFBFBD>嚗?{question}\n\n璉<EFBFBD>蝝W<EFBFBD><EFBFBD><EFBFBD><EFBFBD>獢<EFBFBD><EFBFBD>摰對<EFBFBD>\n${context}`
}
],
temperature: 0.3,
max_tokens: 800
});
const answer = response.choices[0].message.content || '<27>䭾<EFBFBD><E4ADBE><EFBFBD><EFBFBD><EFBFBD>䂿<EFBFBD>';
return `<60><> **<2A>亥<EFBFBD>摨𤘪䰻霂Y<E99C82><EFBCB9>?*\n\n${answer}`;
}
/**
* <20><><EFBFBD><EFBFBD>躰秤<E8BAB0>鞟內
*/
private generateErrorMessage(error?: string): string {
return `<60>?<3F>亥砭憭梯揖
<EFBFBD>笔<EFBFBD>嚗?{error || '<27>芰䰻<E88AB0>躰秤'}
<EFBFBD>働 <20>典虾隞伐<E99A9E>
1. 蝔滚<E89D94><E6BB9A>滩<EFBFBD>
2. <20>V葵<EFBCB6>格<EFBFBD>
3. <20>𠉛頂蝞∠<E89D9E><E288A0>𧜶;
}
}
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>舐<EFBFBD><E88890>鞉㺭<E99E89>格䰻霂W<E99C82>蝑?- <20>?<3F>舐<EFBFBD><E88890>鞟䰻霂<E4B0BB><E99C82>蝝W<E89D9D>蝑?- <20>?<3F>䂿<EFBFBD><E482BF>澆<EFBFBD><E6BE86>见末嚗㇈arkdown嚗?- <20>?<3F>躰秤<E8BAB0>鞟內皜<E585A7>苊
隞餃𦛚3.3嚗𡁶垢<EFBFBD>啁垢瘚贝<EFBFBD>嚗?撠𤩺𧒄嚗?
瘚贝<EFBFBD><EFBFBD>箸艶嚗?
// 瘚贝<E7989A><E8B49D>箸艶1嚗𡁏䰻霂a★<EFBD81>桃<EFBFBD>霈?{
input: "<22>啣銁<E595A3>亦<EFBFBD>憭𡁜<E686AD>鈭箔<E988AD>嚗?,
expectedTool: "query_clinical_data",
expectedIntent: "project_stats",
expectedOutput: "<EFBFBD><EFBFBD> 憿寧𤌍蝏蠘恣<EFBFBD>唳旿\n<EFBFBD>?<EFBFBD>亦<EFBFBD>鈭箸㺭嚗䧥X靘?
}
// 瘚贝<E7989A><E8B49D>箸艶2嚗𡁏䰻霂Y鸌摰𡁏<E691B0><F0A1818F>?{
input: "P001<30><31><EFBFBD><EFBFBD><EFBFBD>摰峕㺭<E5B395>桐<EFBFBD><E6A190>梹<EFBFBD>",
expectedTool: "query_clinical_data",
expectedIntent: "patient_detail",
expectedOutput: "<22>𪈠 <20><><EFBFBD>?P001 霂行<E99C82>"
}
// 瘚贝<E7989A><E8B49D>箸艶3嚗𡁏䰻霂Y<E99C82>蝛嗆䲮獢?{
input: "<22>交<EFBFBD><E4BAA4><EFBFBD><EFBFBD><EFBFBD>臭<EFBFBD>銋<EFBFBD><E98A8B>",
expectedTool: "search_knowledge_base",
expectedCategory: "protocol",
expectedOutput: "<22><> <20>亥<EFBFBD>摨𤘪䰻霂Y<E99C82><EFBCB9>?
}
// 瘚贝<E7989A><E8B49D>箸艶4嚗𡁏䰻霂$RF銵冽聢
{
input: "BMI餈嗘葵摮埈挾<EFBFBD>𦒘<EFBFBD>憛恬<EFBFBD>",
expectedTool: "search_knowledge_base",
expectedCategory: "crf",
expectedOutput: "<EFBFBD><EFBFBD> <EFBFBD>亥<EFBFBD>摨𤘪䰻霂Y<EFBFBD><EFBFBD>?
}
// 瘚贝<E7989A><E8B49D>箸艶5嚗𡁻𤦭<F0A181BB>?{
input: "雿惩末",
expectedTool: null,
expectedOutput: "<22>典末嚗<E69CAB><E59A97><EFBFBD>臭葩摨羓<E691A8>蝛嗅𨭌<E59785>?
}
瘚贝<EFBFBD>甇仿炊嚗?1. <20>其<EFBFBD>銝𡁜凝靽∩葉<E288A9>煾<EFBFBD><E785BE><EFBFBD>霂閙<E99C82><E99699>?2. 閫<><E996AB><EFBFBD>𡒊垢<F0A1928A>亙<EFBFBD> 3. 撉諹<E69289><E8ABB9>𧼮<EFBFBD><F0A7BCAE><EFBFBD>捆 4. 璉<><E79289>亙恣霈⊥𠯫敹? **撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?5銝芣<E98A9D>霂訫㦤<E8A8AB>臬<EFBFBD><E887AC>券<EFBFBD>朞<EFBFBD>
- <EFBFBD>?<3F>𧼮<EFBFBD><F0A7BCAE>園𡢿<3蝘?- <20>?<3F>𧼮<EFBFBD><F0A7BCAE><EFBFBD>捆<EFBFBD><E68D86>&
- <EFBFBD>?摰∟恣<E2889F>亙<EFBFBD>摰峕㟲
Day 4嚗𡁜𪂹<F0A1819C>亥䌊<E4BAA5>典<EFBFBD>獢<EFBFBD><E78DA2>6撠𤩺𧒄嚗?
隞餃𦛚4.1嚗𡁜<EFBFBD><EFBFBD>啣𪂹<EFBFBD>亦<EFBFBD><EFBFBD>𣂼膥嚗?撠𤩺𧒄嚗?
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤backend/src/modules/iit-manager/services/WeeklyReportGenerator.ts`
import { prisma } from '../../../config/database.js';
import { RedcapAdapter } from '../adapters/RedcapAdapter.js';
import { DifyAdapter } from '../adapters/DifyAdapter.js';
import { logger } from '../../../common/logging/index.js';
import { getISOWeek, startOfWeek, endOfWeek } from 'date-fns';
/**
* <20>冽𥁒<E586BD><F0A58192><EFBFBD><EFBFBD>? * <20>芸𢆡<E88AB8><F0A286A1><EFBFBD>憿寧𤌍<E5AFA7>冽𥁒撟嗡<E6929F>隡惩<E99AA1>Dify<66>亥<EFBFBD>摨? */
export class WeeklyReportGenerator {
/**
* <20><><EFBFBD>撟嗡<E6929F>隡惩𪂹<E683A9>? */
async generateAndUpload(projectId: string): Promise<{
success: boolean;
reportId?: string;
error?: string;
}> {
try {
const weekNumber = getISOWeek(new Date());
const year = new Date().getFullYear();
logger.info('[WeeklyReportGenerator] Starting generation', {
projectId,
year,
weekNumber
});
// 1. 璉<><E79289>交糓<E4BAA4>血歇<E8A180><E6AD87><EFBFBD>
const existing = await prisma.iitWeeklyReport.findFirst({
where: {
projectId,
year,
weekNumber
}
});
if (existing) {
logger.warn('[WeeklyReportGenerator] Report already exists', {
reportId: existing.id
});
return {
success: false,
error: '<27>砍𪂹<E7A08D>冽𥁒撌脩<E6928C><E884A9>?
};
}
// 2. <20>園<EFBFBD><E59C92>唳旿
const reportData = await this.collectWeeklyData(projectId, year, weekNumber);
// 3. <20><><EFBFBD>Markdown<77><6E>捆
const content = this.generateMarkdownContent(reportData, year, weekNumber);
// 4. 靽嘥<E99DBD><E598A5>唳㺭<E594B3>桀<EFBFBD>
const report = await prisma.iitWeeklyReport.create({
data: {
projectId,
year,
weekNumber,
content,
stats: reportData.stats,
createdAt: new Date()
}
});
// 5. 銝𠹺<E98A9D><F0A0B9BA>蚤ify<66>亥<EFBFBD>摨? const dify = new DifyAdapter(projectId);
const uploadResult = await dify.uploadDocument(content, {
name: `<60>冽𥁒-${year}撟渡洵${weekNumber}<7D>灼,
doc_type: 'report',
date: `${year}-W${weekNumber.toString().padStart(2, '0')}`
});
if (!uploadResult.success) {
logger.error('[WeeklyReportGenerator] Dify upload failed');
// <20>唳旿摨枏歇靽嘥<E99DBD>嚗㷉ify銝𠹺<E98A9D>憭梯揖銝滚蔣<E6BB9A>? }
logger.info('[WeeklyReportGenerator] Generation completed', {
reportId: report.id,
difyUploaded: uploadResult.success
});
return {
success: true,
reportId: report.id
};
} catch (error: any) {
logger.error('[WeeklyReportGenerator] Generation failed', {
error: error.message,
projectId
});
return {
success: false,
error: error.message
};
}
}
/**
* <20>園<EFBFBD><E59C92>砍𪂹<E7A08D>唳旿
*/
private async collectWeeklyData(
projectId: string,
year: number,
weekNumber: number
): Promise<any> {
const weekStart = startOfWeek(new Date(), { weekStartsOn: 1 });
const weekEnd = endOfWeek(new Date(), { weekStartsOn: 1 });
// 1. <20>瑕<EFBFBD>憿寧𤌍<E5AFA7>滨蔭
const project = await prisma.iitProject.findUnique({
where: { id: projectId },
select: {
name: true,
redcapApiUrl: true,
redcapApiToken: true
}
});
// 2. 隞竃EDCap<61>瑕<EFBFBD>蝏蠘恣
const redcap = new RedcapAdapter(
project!.redcapApiUrl,
project!.redcapApiToken
);
const allRecords = await redcap.exportRecords();
// 3. 隞𤾸恣霈⊥𠯫敹𡑒繮<F0A19192>𡝗𧋦<F0A19D97>冽暑<E586BD>? const weeklyLogs = await prisma.iitAuditLog.findMany({
where: {
projectId,
createdAt: {
gte: weekStart,
lte: weekEnd
}
},
orderBy: { createdAt: 'desc' }
});
// 4. 蝏蠘恣<E8A098>唳旿
const stats = {
totalRecords: allRecords.length,
newRecordsThisWeek: weeklyLogs.filter(
log => log.actionType === 'redcap_data_received'
).length,
qualityIssues: weeklyLogs.filter(
log => log.actionType === 'quality_issue'
).length,
wechatNotifications: weeklyLogs.filter(
log => log.actionType === 'wechat_notification_sent'
).length
};
return {
projectName: project!.name,
year,
weekNumber,
weekStart: weekStart.toISOString(),
weekEnd: weekEnd.toISOString(),
stats,
recentActivities: weeklyLogs.slice(0, 20).map(log => ({
actionType: log.actionType,
entityId: log.entityId,
createdAt: log.createdAt,
details: log.details
}))
};
}
/**
* <20><><EFBFBD>Markdown<77><6E>捆
*/
private generateMarkdownContent(data: any, year: number, weekNumber: number): string {
return `# ${data.projectName} - ${year}撟渡洵${weekNumber}<7D>典𪂹<E585B8>?
## <20><> 蝏蠘恣<E8A098>唳旿
- **<2A>餉扇敶閙㺭**嚗?{data.stats.totalRecords}靘?- **<2A>砍𪂹<E7A08D>啣<EFBFBD>**嚗?{data.stats.newRecordsThisWeek}靘?- **韐冽綉<E586BD>桅<EFBFBD>**嚗?{data.stats.qualityIssues}銝?- **隡<><E99AA1>敺桐縑<E6A190>𡁶䰻**嚗?{data.stats.wechatNotifications}甈?
## <20><> <20>園𡢿<E59C92><F0A1A2BF>凒
- **撘<>憪𧢲𧒄<F0A7A2B2>?*嚗?{new Date(data.weekStart).toLocaleString('zh-CN')}
- **蝏𤘪<E89D8F><F0A498AA>園𡢿**嚗?{new Date(data.weekEnd).toLocaleString('zh-CN')}
## <20><> <20>砍𪂹銝餉<E98A9D>瘣餃𢆡
${data.recentActivities
.map((activity: any, index: number) => {
return `${index + 1}. **${activity.actionType}** - 霈啣<E99C88>${activity.entityId} (${new Date(activity.createdAt).toLocaleString('zh-CN')})`;
})
.join('\n')}
## <20>働 <20>滨<EFBFBD><E6BBA8>單釣
${data.stats.qualityIssues > 0
? `<60>𩤃<EFBFBD> <20>砍𪂹<E7A08D>𤑳緵${data.stats.qualityIssues}銝芾捶<E88ABE>折䔮憸矋<E686B8>霂瑕<E99C82><E79195>嗅<EFBFBD><E59785><EFBFBD>
: '<EFBFBD>?<EFBFBD>砍𪂹<EFBFBD>㰘捶<EFBFBD>折䔮憸?}
---
*<EFBFBD>芸𢆡<EFBFBD><EFBFBD><EFBFBD><EFBFBD>園𡢿嚗?{new Date().toLocaleString('zh-CN')}*`;
}
}
// <20>唳旿摨栞”摰帋<E691B0>嚗<EFBFBD><E59A97>閬<EFBFBD>溶<EFBFBD>惩<EFBFBD>Prisma Schema嚗?/*
model IitWeeklyReport {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
projectId String @db.Uuid
year Int
weekNumber Int
content String @db.Text
stats Json
createdAt DateTime @default(now())
project IitProject @relation(fields: [projectId], references: [id])
@@unique([projectId, year, weekNumber])
@@map("weekly_reports")
@@schema("iit_schema")
}
*/
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>舐<EFBFBD><E88890>𣂼𪂹<F0A382BC>仗arkdown
- <EFBFBD>?<3F>臭<EFBFBD>摮睃<E691AE><E79D83>唳旿摨?- <20>?<3F>臭<EFBFBD>隡惩<E99AA1>Dify
- <EFBFBD>?<3F>脫迫<E884AB>滚<EFBFBD><E6BB9A><EFBFBD><EFBFBD>
隞餃𦛚4.2嚗𡁻<EFBFBD>蝵桀<EFBFBD><EFBFBD>嗡遙<EFBFBD>∴<EFBFBD>2撠𤩺𧒄嚗?
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤backend/src/modules/iit-manager/index.ts`
import cron from 'node-cron';
import { WeeklyReportGenerator } from './services/WeeklyReportGenerator.js';
/**
* <20>嘥<EFBFBD><E598A5>𦎾IT Manager璅∪<E79285>
*/
export async function initIitManager() {
// ... <20>唳<EFBFBD><E594B3><EFBFBD>orker瘜典<E7989C>隞<EFBFBD><E99A9E> ...
// <20>?<3F>啣<EFBFBD>嚗𡁏釣<F0A1818F><E987A3>𪂹<EFBFBD>亙<EFBFBD><E4BA99>嗡遙<E597A1>? registerWeeklyReportCron();
logger.info('<27>?IIT Manager initialized');
}
/**
* 瘜典<E7989C><E585B8>冽𥁒摰𡁏𧒄隞餃𦛚
* 瘥誩𪂹銝<F0AA82B9> 00:00 <20>芸𢆡<E88AB8><F0A286A1><EFBFBD>銝𠰴𪂹<F0A0B0B4>冽𥁒
*/
function registerWeeklyReportCron() {
const generator = new WeeklyReportGenerator();
// 瘥誩𪂹銝<F0AA82B9><E98A9D>峕膥0<E886A5>寞<EFBFBD>銵? cron.schedule('0 0 * * 1', async () => {
logger.info('<27>?撘<>憪讠<E686AA><E8AEA0>𣂼𪂹<F0A382BC>?);
try {
// <20>瑕<EFBFBD><E79195><EFBFBD><EFBFBD>㗇暑頝<E69A91>★<EFBFBD>? const projects = await prisma.iitProject.findMany({
where: { status: 'active' }
});
for (const project of projects) {
await generator.generateAndUpload(project.id);
}
logger.info('<EFBFBD>?<EFBFBD>冽𥁒<EFBFBD><EFBFBD><EFBFBD>摰峕<EFBFBD>', {
projectCount: projects.length
});
} catch (error: any) {
logger.error('<EFBFBD>?<EFBFBD>冽𥁒<EFBFBD><EFBFBD><EFBFBD>憭梯揖', {
error: error.message
});
}
}, {
timezone: 'Asia/Shanghai'
});
logger.info('<EFBFBD>?<EFBFBD>冽𥁒摰𡁏𧒄隞餃𦛚撌脫釣<EFBFBD>䕘<EFBFBD>瘥誩𪂹銝<EFBFBD> 00:00嚗?);
}
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?摰𡁏𧒄隞餃𦛚瘜典<E7989C><E585B8>𣂼<EFBFBD>
- <EFBFBD>?<3F>舀<EFBFBD><E88880>刻圻<E588BB>烐<EFBFBD>霂?- <20>?<3F>亙<EFBFBD>霈啣<E99C88>摰峕㟲
Day 5嚗𡁏<E59A97>獢<EFBFBD><E78DA2><EFBFBD>嗘<EFBFBD>瘚贝<E7989A>嚗?撠𤩺𧒄嚗?
隞餃𦛚5.1嚗𡁶鍂<EFBFBD>瑚蝙<EFBFBD>冽<EFBFBD><EFBFBD>䕘<EFBFBD>2撠𤩺𧒄嚗?
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤AIclinicalresearch/docs/03-銝𡁜𦛚璅∪<E79285>/IIT Manager Agent/05-雿輻鍂<E8BCBB>见<EFBFBD>/隡<><E99AA1>敺桐縑撖寡<E69296><E5AFA1><EFBFBD><EFBFBD>.md`
<EFBFBD><EFBFBD>捆憭抒熔嚗?1. <20>蠘<EFBFBD>隞讠<E99A9E> 2. <20>舀<EFBFBD><E88880><EFBFBD>䰻霂Y掩<EFBCB9>?3. 撣貊鍂<E8B28A>格<EFBFBD>蝷箔<E89DB7> 4. 瘜冽<E7989C>鈭钅★ 5. 撣貉<E692A3><E8B289>桅<EFBFBD>FAQ
隞餃𦛚5.2嚗䥪hase 1.5撘<EFBFBD><EFBFBD>𤏸扇敶𤏪<EFBFBD>2撠𤩺𧒄嚗?
<EFBFBD><EFBFBD>辣雿滨蔭嚗䫤AIclinicalresearch/docs/03-銝𡁜𦛚璅∪<E79285>/IIT Manager Agent/06-撘<><E69298>𤏸扇敶?Phase1.5-AI撖寡<E69296><E5AFA1>賢<EFBFBD>撘<EFBFBD><E69298>穃<EFBFBD><E7A983>鞱扇敶?md`
<EFBFBD><EFBFBD>捆憭抒熔嚗?1. 撘<><E69298>𤑳𤌍<F0A491B3><F0A48C8D><EFBFBD><EFBFBD>鞉<EFBFBD> 2. <20><><EFBFBD>臬<EFBFBD><E887AC>啁<EFBFBD><E59581>?3. 瘚贝<E7989A>撉諹<E69289>蝏𤘪<E89D8F> 4. 撌脩䰻<E884A9>𣂼<EFBFBD>銝擧㺿餈𥡝恣<F0A5A19D>?
隞餃𦛚5.3嚗𡁜<EFBFBD><EFBFBD>湔<EFBFBD>霂𤏪<EFBFBD>2撠𤩺𧒄嚗?
瘚贝<EFBFBD><EFBFBD>拚猐嚗?
| <EFBFBD>箸艶 | 颲枏<EFBFBD> | 憸<EFBFBD><EFBFBD>撌亙<EFBFBD> | 憸<EFBFBD><EFBFBD>颲枏枂 | <EFBFBD>嗆<EFBFBD>? |
|---|---|---|---|---|
| 憿寧𤌍蝏蠘恣 | "<22>啣銁<E595A3>亦<EFBFBD>憭𡁜<E686AD>鈭綽<E988AD>" | query_clinical_data | <EFBFBD><EFBFBD>鉄<EFBFBD>亦<EFBFBD>鈭箸㺭 | <EFBFBD>? |
| <EFBFBD><EFBFBD><EFBFBD><EFBFBD>祕<EFBFBD>? | "P001敶訫<E695B6>鈭<EFBFBD><E988AD>嚗? | query_clinical_data | <EFBFBD><EFBFBD>鉄<EFBFBD><EFBFBD><EFBFBD><EFBFBD>𠶖<EFBFBD>? | <EFBFBD>? |
| 韐冽綉<EFBFBD>嗆<EFBFBD>? | "<22>匧𪑛鈭𥡝捶<F0A5A19D>折䔮憸矋<E686B8>" | query_clinical_data | <EFBFBD>桅<EFBFBD><EFBFBD>𡑒” | <EFBFBD>? |
| <EFBFBD>𠉛弦<EFBFBD>寞<EFBFBD> | "<22>交<EFBFBD><E4BAA4><EFBFBD><EFBFBD><EFBFBD>臭<EFBFBD>銋<EFBFBD><E98A8B>" | search_knowledge_base | <EFBFBD>寞<EFBFBD><EFBFBD><EFBFBD>捆 | <EFBFBD>? |
| CRF<EFBFBD>亥砭 | "BMI<4D>𦒘<EFBFBD>憛恬<E6869B>" | search_knowledge_base | CRF霂湔<EFBFBD> | <EFBFBD>? |
| <EFBFBD>冽𥁒<EFBFBD>亥砭 | "銝𠰴𪂹餈𥕦<E9A488>憒<EFBFBD><E68692>嚗? | search_knowledge_base | <EFBFBD>冽𥁒<EFBFBD><EFBFBD>捆 | <EFBFBD>? |
| <EFBFBD>脰<EFBFBD> | "雿惩末" | <EFBFBD>? | <EFBFBD>见末<EFBFBD>𧼮<EFBFBD> | <EFBFBD>? |
<EFBFBD><EFBFBD> <20>䜘<EFBFBD><E49C98><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>撉峕𤣰
4.1 <20>蠘<EFBFBD>摰峕㟲<E5B395>?
| <EFBFBD>蠘<EFBFBD> | 撉峕𤣰<EFBFBD><EFBFBD><EFBFBD> | 隡睃<EFBFBD>蝥? |
|---|---|---|
| Dify<EFBFBD><EFBFBD><EFBFBD> | <EFBFBD>舀<EFBFBD><EFBFBD>蠘<EFBFBD><EFBFBD>冽𧋦<EFBFBD>蚤ify API | <EFBFBD>𣞁 P0 |
| <EFBFBD>誩㦛霂<EFBFBD><EFBFBD> | <EFBFBD><EFBFBD>&<EFBFBD>?80% | <EFBFBD>𣞁 P0 |
| <EFBFBD>唳旿<EFBFBD>亥砭 | <EFBFBD>舀䰻霂㎞EDCap摰墧𧒄<EFBFBD>唳旿 | <EFBFBD>𣞁 P0 |
| *<EFBFBD>亥<EFBFBD>璉<EFBFBD>蝝? | <EFBFBD>舀<EFBFBD>蝝Y<EFBFBD>蝛嗆䲮獢<EFBFBD><EFBFBD>獢? | <EFBFBD>𣞁 P0 |
| 隡<EFBFBD><EFBFBD>敺桐縑<EFBFBD>𧼮<EFBFBD> | <EFBFBD>𧼮<EFBFBD><EFBFBD>園𡢿<3蝘? | <EFBFBD>𣞁 P0 |
| <EFBFBD>冽𥁒<EFBFBD>芸𢆡敶埝﹝ | 瘥誩𪂹銝<EFBFBD><EFBFBD>芸𢆡<EFBFBD><EFBFBD><EFBFBD> | <EFBFBD><EFBFBD> P1 |
| 摰∟恣<EFBFBD>亙<EFBFBD> | <EFBFBD><EFBFBD><EFBFBD>匧笆霂脲<EFBFBD><EFBFBD>亙<EFBFBD> | <EFBFBD><EFBFBD> P1 |
4.2 <20>扯<EFBFBD><E689AF><EFBFBD><EFBFBD>
| <EFBFBD><EFBFBD><EFBFBD> | <EFBFBD>格<EFBFBD> | 霂湔<EFBFBD> |
|---|---|---|
| <EFBFBD>𧼮<EFBFBD>撱嗉<EFBFBD> | <3蝘? | <EFBFBD>冽<EFBFBD><EFBFBD>?<3F>?<3F>嗅<EFBFBD><E59785>𧼮<EFBFBD> |
| Dify<EFBFBD>亥砭撱嗉<EFBFBD> | <500ms | <EFBFBD>砍𧑐<EFBFBD>函蔡嚗<EFBFBD><EFBFBD>霂亙<EFBFBD>敹? |
| REDCap<EFBFBD>亥砭撱嗉<EFBFBD> | <1蝘? | 撌脫<EFBFBD>adapter嚗<EFBFBD>歇撉諹<EFBFBD> |
| *<EFBFBD>誩㦛霂<EFBFBD><EFBFBD><EFBFBD><EFBFBD>&<EFBFBD>? | >80% | <EFBFBD>朞<EFBFBD>瘚贝<EFBFBD><EFBFBD>拚猐撉諹<EFBFBD> |
| <EFBFBD>亥<EFBFBD>璉<EFBFBD>蝝W<EFBFBD>蝖桃<EFBFBD> | >70% | 靘肽<EFBFBD><EFBFBD><EFBFBD>﹝韐券<EFBFBD> |
4.3 隞<><E99A9E>韐券<E99F90>
- <EFBFBD>?TypeScript蝐餃<E89D90>摰峕㟲
- <EFBFBD>?<3F>訫<EFBFBD>瘚贝<E7989A>閬<EFBFBD><E996AC><EFBFBD>?70%
- <EFBFBD>?<3F><><EFBFBD>瘚贝<E7989A><E8B49D>朞<EFBFBD>
- <EFBFBD>?<3F>躰秤憭<E7A7A4><E686AD>摰<EFBFBD><E691B0>
- <EFBFBD>?<3F>亙<EFBFBD>霈啣<E99C88>摰峕㟲
- <EFBFBD>?隞<><E99A9E>蝚血<E89D9A>閫<EFBFBD><E996AB>
<EFBFBD><EFBFBD> 鈭𢛵<E988AD><F0A29BB5><EFBFBD>蝵脖<E89DB5>銝羓瑪
5.1 <20>臬<EFBFBD><E887AC>㗛<EFBFBD><E3979B>滨蔭
# Dify<66>滨蔭
DIFY_API_URL=http://localhost/v1
DIFY_API_KEY=app-xxxxxxxxxxxxxxxxxxxxxx
DIFY_KNOWLEDGE_BASE_ID=kb-xxxxxxxxxxxxxxxxxxxxxx
# LLM<4C>滨蔭嚗㇄eepSeek嚗?OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxx
OPENAI_BASE_URL=https://api.deepseek.com
# 隡<><E99AA1>敺桐縑<E6A190>滨蔭嚗<E894AD>歇<EFBFBD>㚁<EFBFBD>
WECHAT_CORP_ID=ww01cb7b72ea2db83c
WECHAT_CORP_SECRET=xxx
WECHAT_AGENT_ID=1000002
5.2 <20>唳旿摨栞<E691A8>蝘?
# 瘛餃<E7989B><E9A483>冽𥁒銵?npx prisma db push
npx prisma generate
5.3 <20>滚鍳<E6BB9A>滚𦛚
cd AIclinicalresearch/backend
npm run dev
<EFBFBD><EFBFBD> <20>准<EFBFBD><E58786><EFBFBD><EFBFBD>臬<EFBFBD>箏𦛚銝擧㺿餈𥡝恣<F0A5A19D>?
6.1 敶枏<E695B6><E69E8F>𣂼<EFBFBD>嚗㇊hase 1.5嚗?
| <EFBFBD>𣂼<EFBFBD> | 敶勗<EFBFBD> | 霈∪<EFBFBD><EFBFBD>寡<EFBFBD><EFBFBD>園𡢿 |
|---|---|---|
| *<EFBFBD>閖★<EFBFBD>格𣈲<EFBFBD>? | <EFBFBD>芾<EFBFBD><EFBFBD>滚𦛚銝<EFBFBD>銝芷★<EFBFBD>? | Phase 2 |
| *<EFBFBD>惩<EFBFBD>頧桀笆霂? | 瘥𤩺活<EFBFBD>桃<EFBFBD><EFBFBD>祉<EFBFBD> | Phase 2 |
| <EFBFBD>牐<EFBFBD>銝𧢲<EFBFBD>霈啣<EFBFBD> | 銝滩扇敺𦯀<EFBFBD><EFBFBD>滨<EFBFBD>撖寡<EFBFBD> | Phase 2 |
| 蝖祉<EFBFBD><EFBFBD><EFBFBD>䰻霂<EFBFBD><EFBFBD>ID | 銝齿𣈲<EFBFBD><EFBFBD><EFBFBD>憿寧𤌍 | Phase 2 |
| *蝞<EFBFBD><EFBFBD>閙<EFBFBD><EFBFBD>曇<EFBFBD><EFBFBD>? | 銝齿𣈲<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>綫<EFBFBD>? | Phase 3 |
6.2 Phase 2 <20>寡<EFBFBD>霈∪<E99C88>
-
*憭𡁻★<EFBFBD>格𣈲<EFBFBD>?
- 瘥譍葵憿寧𤌍<EFBFBD>祉<EFBFBD><EFBFBD>亥<EFBFBD>摨? - <20>冽<EFBFBD><E586BD><EFBFBD><EFBFBD>蝞∠<E89D9E>
- 憿寧𤌍<EFBFBD><EFBFBD>揢<EFBFBD>蠘<EFBFBD>
-
憭朞蔭撖寡<EFBFBD>
- 隡朞<EFBFBD><EFBFBD>嗆<EFBFBD><EFBFBD>恣<EFBFBD>? - 銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹<E68987><E695B9>Redis嚗? - 瞉<><E79E89>撘𤩺<E69298><F0A4A9BA>?
-
*瘛瑕<EFBFBD><EFBFBD>函<EFBFBD>嚗㇌eAct嚗?
- <EFBFBD>舀<EFBFBD>憭齿<EFBFBD><EFBFBD>亥砭
- 憭𡁜極<EFBFBD>瑞<EFBFBD><EFBFBD>? - <20>芯蜓<E88AAF>函<EFBFBD>敺芰㴓
<EFBFBD>?銝<><E98A9D><EFBFBD><EFBFBD>餌<EFBFBD>
7.1 Phase 1.5<EFBFBD>詨<EFBFBD>隞瑕<EFBFBD>?
**摰䂿緵<E482BF>格<EFBFBD>**嚗?- <20>?PI<50>臬銁隡<E98A81><E99AA1>敺桐縑銝剛䌊<E5899B>嗉祗閮<E7A597><E996AE>鞾䔮
- <EFBFBD>?AI<41>舐<EFBFBD>閫<EFBFBD><E996AB><EFBFBD>曉僎<E69B89>亥砭<E4BAA5>唳旿/<2F><>﹝
- <EFBFBD>?<3F>䂿<EFBFBD><E482BF><EFBFBD>&<EFBFBD><EFBC86><EFBFBD><EFBFBD>嗚<EFBFBD><E5979A><EFBFBD>憟?- <20>?<3F>冽𥁒<E586BD>芸𢆡敶埝﹝嚗<EFB99D>虾<EFBFBD>𤩺𧒄<F0A4A9BA>亥砭
**<2A><><EFBFBD>臭漁<E887AD>?*嚗?- <20>㴓 <20>箔<EFBFBD><E7AE94>砍𧑐Dify嚗峕<E59A97>API<50>鞉𧋦
- <EFBFBD><EFBFBD> <20>閙郊<E99699>誩㦛霂<E3A69B><E99C82>嚗𣬚<E59A97><F0A3AC9A>閖<EFBFBD><E99696>?- <20>圲 憭滨鍂<E6BBA8>唳<EFBFBD>RedcapAdapter
- <EFBFBD><EFBFBD> <20>冽𥁒<E586BD>芸𢆡<E88AB8><F0A286A1><EFBFBD>銝𤾸<E98A9D>獢?- <20><> 蝡臬<E89DA1>蝡臬辣餈?3蝘? **撘<><E69298>烐<EFBFBD><E78390>?*嚗?- <20><> 憸<>摯撌乩<E6928C><E4B9A9>𧶏<EFBFBD>5憭?- <20><> <20>啣<EFBFBD>隞<EFBFBD><E99A9E>嚗鰺2000銵?- <20>妒 瘚贝<E7989A>閬<EFBFBD><E996AC>嚗?70%
- <EFBFBD><EFBFBD> <20><>﹝摰峕㟲嚗𡁶鍂<F0A181B6>瑟<EFBFBD><E7919F>?撘<><E69298>𤏸扇敶?
**銝衤<E98A9D>甇?*嚗䥪hase 2 - 憭𡁻★<F0A181BB>格𣈲<E6A0BC><F0A388B2><EFBFBD>擃条漣撖寡<E69296><E5AFA1>賢<EFBFBD>
<EFBFBD>㴓 <20>怒<EFBFBD><E68092><EFBFBD>蝞<EFBFBD><E89D9E><EFBFBD>瓲敹<E793B2>遠<EFBFBD>潭<EFBFBD>餌<EFBFBD>
8.1 銝箔<E98A9D>銋<EFBFBD><E98A8B>蝞<EFBFBD><E89D9E><EFBFBD><EFBFBD><EFBFBD>滩<EFBFBD>嚗?
**<2A>冽<EFBFBD><E586BD>漤<EFBFBD>**嚗?> "<22><><EFBFBD>滩<EFBFBD><E6BBA9><EFBFBD>糓<EFBFBD><E7B393>悟AI<41>賢笆霂嘅<E99C82><E59885>嗡<EFBFBD><E597A1>賢<EFBFBD><E8B3A2>曆<EFBFBD>颲?
**<2A>啣<EFBFBD><E595A3><EFBFBD><EFBFBD>**嚗?- <20>?MVP<56>剔㴓撌脫<E6928C><E884AB>𡄯<EFBFBD>Day 3摰峕<E691B0>嚗?- <20>?隡<><E99AA1>敺桐縑<E6A190>券<EFBFBD><E588B8>歇撉諹<E69289>嚗?00%<25>𣂼<EFBFBD><F0A382BC><EFBFBD><EFBFBD>
- <EFBFBD>?RedcapAdapter撌脣虾<E884A3>剁<EFBFBD><E58981>湔𦻖憭滨鍂嚗?- <20>𩤃<EFBFBD> **蝻箏<E89DBB>**嚗䥪I<E4A5AA>䭾<EFBFBD>銝餃𢆡<E9A483>亥砭<E4BAA5>唳旿
**<2A><><EFBFBD><EFBFBD><EFBFBD>遠<EFBFBD>?*嚗?- <20><> **2憭拐<E686AD>蝥?*嚗𡁏<E59A97>敹怠<E695B9><E680A0>蚊I撖寡<E69296>
- <EFBFBD>兛 **<2A>嗆<EFBFBD><E59786>?*嚗𡁜蘨<F0A1819C>充EDCap嚗䔶<E59A97><E494B6>求ify
- <EFBFBD><EFBFBD> **<2A>㕑扇敹?*嚗𡁏𣈲<F0A1818F><F0A388B2><EFBFBD>頧桀笆霂嘅<E99C82>3頧殷<E9A0A7>
- <EFBFBD>?**<2A>匧<EFBFBD>擐?*嚗?甇<>銁<EFBFBD>亥砭..."<22>踹<EFBFBD><E8B8B9>冽<EFBFBD><E586BD>西<EFBFBD>
- <EFBFBD>㴓 <EFBFBD>詨<EFBFBD>憭毺鍂嚗𡁏說頞?0%<25><>䰻霂a<E99C82>瘙?
8.2 銝匧之<E58CA7>詨<EFBFBD><E8A9A8>寡<EFBFBD>嚗<EFBFBD>抅鈭𡒊鍂<F0A1928A>瑕遣霈殷<E99C88>
<EFBFBD>寡<EFBFBD>1嚗帋<EFBFBD>銝𧢲<EFBFBD>霈啣<EFBFBD> <20>?
**<2A>桅<EFBFBD>**嚗? PI: "撣格<E692A3><E6A0BC>乩<EFBFBD>銝閪001<30><31><EFBFBD>蝏<EFBFBD><E89D8F><EFBFBD>? AI: "P001撌脣<E6928C>蝏? PI: "隞𡝗<E99A9E>銝滩<E98A9D><E6BBA9>滚<EFBFBD><E6BB9A>梹<EFBFBD>" AI: <20>?"霂瑟<E99C82>靘𥟇<E99D98><F0A59F87><EFBFBD><EFBFBD><EFBFBD>?嚗<>仃敹<E4BB83><E695B9>嚗?
**閫<><E996AB>**嚗?```typescript // SessionMemory嚗𡁜<E59A97><F0A1819C>冽<EFBFBD>餈?頧桀笆霂?sessionMemory.addMessage(userId, 'user', '撣格<E692A3><E6A0BC>仙001'); sessionMemory.addMessage(userId, 'assistant', 'P001撌脣<E6928C>蝏?);
// 銝𧢲活<F0A7A2B2>亥砭<E4BAA5>塚<EFBFBD><E5A19A>芸𢆡憛怠<E6869B><E680A0><EFBFBD><EFBFBD><EFBFBD>D const lastPatientId = sessionMemory.getLastPatientId(userId); // P001
**<2A><><EFBFBD>**嚗?```
PI: "隞𡝗<E99A9E>銝滩<E98A9D><E6BBA9>滚<EFBFBD><E6BB9A>梹<EFBFBD>"
AI: <20>?"<22>亥砭P001嚗𡁏<E59A97>銝滩<E98A9D><E6BBA9>滚<EFBFBD>霈啣<E99C88>"嚗<>扇敺埈糓P001嚗?```
---
#### <20>寡<EFBFBD>2嚗𡁏迤<F0A1818F>刻<EFBFBD><E588BB>亙<EFBFBD>擐?<3F>?
**<2A>桅<EFBFBD>**嚗?- AI憭<49><E686AD><EFBFBD><EFBFBD>閬?-8蝘?- <20>冽<EFBFBD><E586BD>穃<EFBFBD>瘨<EFBFBD><E798A8><EFBFBD>𠬍<EFBFBD><F0A0AC8D>𧢲㦤瘝∪<E7989D>摨?- <20>冽<EFBFBD>隞乩蛹蝟餌<E89D9F><E9A48C><EFBFBD><EFBFBD>
**閫<><E996AB>**嚗?```typescript
// 蝡见朖<E8A781>煾<EFBFBD><E785BE>葩<EFBFBD>嗅<EFBFBD>擐?await wechatService.sendTextMessage(userId, '<27>哄 甇<>銁<EFBFBD>亥砭嚗諹窈蝔滚<E89D94>?..');
// <20>齿<EFBFBD><E9BDBF>W<EFBFBD><EFBCB7>?const answer = await processQuery(userMessage);
await wechatService.sendMarkdownMessage(userId, answer);
**<2A><><EFBFBD>**嚗?- <20>?<3F>冽<EFBFBD>蝡见朖<E8A781>见<EFBFBD><E8A781>漤<EFBFBD>嚗?1蝘𡜐<E89D98>
- <EFBFBD>?<3F>仿<EFBFBD>AI<41>典極雿?- <20>?銝滢<E98A9D><E6BBA2>西<EFBFBD>
<EFBFBD>寡<EFBFBD>3嚗𡁏<EFBFBD>蝞<EFBFBD>隡睃<EFBFBD> <20>?
**<2A>桅<EFBFBD>**嚗?- <20>蠘恣<E8A098>?憭拙<E686AD><E68B99>穃云<E7A983>?- Dify<66><79>𪂹<EFBFBD>亦<EFBFBD><E4BAA6>蠘<EFBFBD><E8A098>𧼮<EFBFBD><F0A7BCAE><EFBFBD>
- <EFBFBD>冽<EFBFBD><EFBFBD><EFBFBD><EFBFBD>唾<EFBFBD>嚗朞<EFBFBD>撖寡<EFBFBD>
**閫<><E996AB>**嚗?- <20>?<3F>芸<EFBFBD>REDCap<61>亥砭嚗<E7A0AD><E59A97><EFBFBD>函緵<E587BD>学dapter嚗?- <20>?銝齿𦻖Dify嚗㇊hase 2<>滚<EFBFBD>嚗?- <20>?銝滚<E98A9D><E6BB9A>冽𥁒嚗㇊hase 2<>滚<EFBFBD>嚗?- <20>?2憭拐<E686AD>蝥? **<2A><><EFBFBD>**嚗?- <20>?敹恍<E695B9>罸<EFBFBD>霂<EFBFBD>遠<EFBFBD>?- <20>?敹恍<E695B9><E6818D>𤣰<EFBFBD><F0A4A3B0><EFBFBD>擐?- <20>?敹恍<E695B9>蠘翮隞?
8.3 <20>滨垢<E6BBA8>嗆<EFBFBD>瞍磰<E79E8D>頝舐瑪
Phase 1.5嚗<EFBFBD><EFBFBD><EFBFBD>㵪<EFBFBD>:
<0A>鎿<EFBFBD><E98EBF><EFBFBD> 隡<><E99AA1>敺桐縑<E6A190>毺<EFBFBD>撖寡<E69296>嚗<EFBFBD><E59A97>鈭页<E988AD>
<0A>鎿<EFBFBD><E98EBF><EFBFBD> <20>㰘䌊摰帋<E691B0>UI
<0A>婙<EFBFBD><E5A999><EFBFBD> 銝𠹺<E98A9D><F0A0B9BA><EFBFBD><EFBFBD><EFBFBD>沐ode.js<6A><73><EFBFBD>
<20>?嚗<>鍂<EFBFBD>瑕<EFBFBD>擐?+ <20><>瘙<EFBFBD><E79899><EFBFBD>選<EFBFBD>
Phase 3嚗<33>𧊋<EFBFBD>伐<EFBFBD>:
<0A>鎿<EFBFBD><E98EBF><EFBFBD> <20>芰<EFBFBD>H5/撠讐<E692A0>摨𧶏<E691A8>Taro 4.x嚗?<3F>鎿<EFBFBD><E98EBF><EFBFBD> Ant Design X蝞∠<E89D9E>銝𠹺<E98A9D><F0A0B9BA>?<3F>鎿<EFBFBD><E98EBF><EFBFBD> 銝啣<E98A9D><E595A3><EFBFBD>I蝏<49>辣嚗<E8BEA3><E59A97><EFBFBD>交<EFBFBD>蝷箝<E89DB7><E7AE9D><EFBFBD><EFBFBD>脰扇敶𨰻<E695B6><F0A8B0BB>䰻霂<E4B0BB>㨃<EFBFBD><E3A883><EFBFBD>
<0A>婙<EFBFBD><E5A999><EFBFBD> <20>游末<E6B8B8><E69CAB>鍂<EFBFBD>瑚<EFBFBD>撉?```
**銝箔<E98A9D>銋<EFBFBD><E98A8B>銝日𧫴畾蛛<E795BE>**
- <20>?Phase 1.5嚗𡁻<EFBFBD>霂<EFBFBD>瓲敹<EFBFBD>遠<EFBFBD>潘<EFBFBD>AI<EFBFBD>賢<EFBFBD>蝑娪䔮憸矋<EFBFBD>
- <20>?Phase 3嚗帋<E59A97><E5B88B>𣇉鍂<F0A38789>瑚<EFBFBD>撉䕘<E69289><E49598>渡<EFBFBD>閫<EFBFBD><E996AB><EFBFBD>凒<EFBFBD>箄<EFBFBD>嚗?- <20>?<3F>踹<EFBFBD>餈<EFBFBD>漲霈曇恣嚗<E681A3><E59A97><EFBFBD>匧<EFBFBD>憟踝<E6869F>
---
### 8.4 蝡见朖銵<E69C96>𢆡<EFBFBD><F0A286A1><EFBFBD>
#### Step 1嚗𡁜<E59A97>撱箇洵銝<E6B4B5>銝芣<E98A9D>隞塚<E99A9E>5<EFBFBD><35><EFBFBD>嚗?
```bash
cd AIclinicalresearch/backend/src/modules/iit-manager
mkdir -p agents
touch agents/SessionMemory.ts
憭滚<EFBFBD>Day 1隞餃𦛚1.1<EFBFBD><EFBFBD>誨<EFBFBD><EFBFBD><EFBFBD> SessionMemory.ts
Step 2嚗朞<E59A97>銵<EFBFBD><E98AB5><EFBFBD><EFBFBD><EFBFBD>霂𤏪<E99C82><F0A48FAA>舫<EFBFBD>㚁<EFBFBD>
npm test agents/SessionMemory.test.ts
Step 3嚗𡁶誧蝏胖ay 1<>嗡<EFBFBD>隞餃𦛚
<EFBFBD>厩<EFBFBD><EFBFBD><EFBFBD>﹝銝剔<EFBFBD>憿箏<EFBFBD>嚗?1. <20>?SessionMemory (30<33><30><EFBFBD>) 2. <20>?SimpleIntentRouter (2撠𤩺𧒄) 3. <20>?SimpleToolExecutor (1.5撠𤩺𧒄) 4. <20>?SimpleAnswerGenerator (1撠𤩺𧒄) 5. <20>?<3F><><EFBFBD><EFBFBD>訖echatCallbackController (1撠𤩺𧒄)
Step 4嚗鋽ay 1蝏𤘪<E89D8F><F0A498AA>嗆<EFBFBD>霂?
<EFBFBD>其<EFBFBD>銝𡁜凝靽∩葉<EFBFBD>煾<EFBFBD><EFBFBD><EFBFBD>
"<22>啣銁<E595A3>亦<EFBFBD>憭𡁜<E686AD>鈭綽<E988AD>"
憸<EFBFBD><EFBFBD>嚗?1. 蝡见朖<E8A781>嗅<EFBFBD>"<22>哄 甇<>銁<EFBFBD>亥砭嚗諹窈蝔滚<E89D94>?.." 2. 3蝘鍦<E89D98><E98DA6>嗅<EFBFBD>"<22><> 憿寧𤌍蝏蠘恣..."
Step 5嚗鋽ay 2瘚贝<E7989A>憭朞蔭撖寡<E69296>
PI: "撣格<E692A3><E6A0BC>乩<EFBFBD>銝閪001<30><31><EFBFBD><EFBFBD>?
AI: "<22>𪈠 <20><><EFBFBD>?P001 霂行<E99C82>..."
PI: "隞𡝗<E99A9E>銝滩<E98A9D><E6BBA9>滚<EFBFBD><E6BB9A>梹<EFBFBD>" <20>?瘚贝<E7989A>銝𠹺<E98A9D><F0A0B9BA>?AI: "<22>亥砭P001嚗𡁏<E59A97>銝滩<E98A9D><E6BBA9>滚<EFBFBD>霈啣<E99C88>" <20>?摨磰砲<E7A3B0>芸𢆡霂<F0A286A1><E99C82>
8.5 <20>𣂼<EFBFBD><F0A382BC><EFBFBD><EFBFBD>嚗<EFBFBD><E59A97>蝞<EFBFBD><E89D9E><EFBFBD><EFBFBD>
| 璉<EFBFBD><EFBFBD>仿★ | <EFBFBD><EFBFBD><EFBFBD> | 撉諹<EFBFBD><EFBFBD>孵<EFBFBD> |
|---|---|---|
| <EFBFBD>?<3F>箇<EFBFBD>撖寡<E69296> | <EFBFBD>賢<EFBFBD>蝑?<3F>亦<EFBFBD>憭𡁜<E686AD>鈭? | 隡<EFBFBD>凝瘚贝<EFBFBD> |
| <EFBFBD>?<3F><><EFBFBD><EFBFBD>䰻霂? | <EFBFBD>賢<EFBFBD>蝑?P001<30><31><EFBFBD>" | 隡<EFBFBD>凝瘚贝<EFBFBD> |
| <EFBFBD>?銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹? | "隞𡝗<E99A9E>銝滩<E98A9D><E6BBA9>滚<EFBFBD><E6BB9A>?<3F>質<EFBFBD><E8B3AA>促001 | 隡<EFBFBD>凝瘚贝<EFBFBD> |
| <EFBFBD>?甇<>銁颲枏<E9A2B2><E69E8F>漤<EFBFBD> | <1蝘埝𤣰<E59F9D>?甇<>銁<EFBFBD>亥砭..." | 隡<EFBFBD>凝瘚贝<EFBFBD> |
| <EFBFBD>?<3F><>蝏<EFBFBD><E89D8F>憭? | <3蝘埝𤣰<E59F9D>啣<EFBFBD><E595A3>渡<EFBFBD>獢? | <EFBFBD>𡒊垢<EFBFBD>亙<EFBFBD> |
| <EFBFBD>?摰∟恣<E2889F>亙<EFBFBD> | 霈啣<EFBFBD>銝𠹺<EFBFBD><EFBFBD><EFBFBD><EFBFBD>霈? | <EFBFBD>唳旿摨𤘪<EFBFBD><EFBFBD>? |
8.6 銝𤾸<E98A9D><F0A4BEB8>渡<EFBFBD><E6B8A1><EFBFBD><EFBFBD>蝟?
**<2A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2憭抬<E686AD>**嚗?- <20>㴓 <20>格<EFBFBD>嚗𡁏<E59A97>敹恍<E695B9>霂<EFBFBD>I撖寡<E69296>隞瑕<E99A9E>?- <20>𣑐 <20><>凒嚗鑹EDCap<61>亥砭 + 銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹?- <20>兛 <20>鞉𧋦嚗𡁏<E59A97>憸嘥<E686B8><E598A5>鞉𧋦嚗<F0A78BA6><E59A97><EFBFBD>函緵<E587BD>㚁<EFBFBD>
- <EFBFBD><EFBFBD> <20>笔漲嚗?憭拐<E686AD>蝥? **摰峕㟲<E5B395><E39FB2><EFBFBD>5憭抬<E686AD>**嚗?- <20>㴓 <20>格<EFBFBD>嚗𡁜<E59A97><F0A1819C>Y<EFBFBD>AI<41>拇<EFBFBD><E68B87>賢<EFBFBD>
- <EFBFBD>𣑐 <20><>凒嚗? Dify<66>亥<EFBFBD>摨?+ <20>冽𥁒敶埝﹝ + <20><>﹝<EFBFBD>亥砭
- <EFBFBD>兛 <20>鞉𧋦嚗𡁻<E59A97><F0A181BB>滨蔭Dify嚗<79>歇<EFBFBD><E6AD87>ocker嚗?- <20><> <20>笔漲嚗?憭拐<E686AD>蝥?
撱箄悅嚗?<3F>?**<2A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?*嚗?憭抬<E686AD>嚗屸<E59A97>霂<EFBFBD>遠<EFBFBD>?
<0A>?<3F>園<EFBFBD><E59C92>冽<EFBFBD><E586BD>漤<EFBFBD>
<0A>?<3F>滚<EFBFBD>摰𡁏糓<F0A1818F>血<EFBFBD>摰峕㟲<E5B395>? **摰鮋<E691B0><E9AE8B>扯<EFBFBD>嚗?<3F>?<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>歇摰峕<EFBFBD>**嚗?026-01-03嚗? <0A>?**Dify<66>亥<EFBFBD>摨枏歇<E69E8F><E6AD87><EFBFBD>**嚗?026-01-04嚗? <0A>?瘛瑕<EFBFBD>璉<EFBFBD>蝝W歇摰䂿緵嚗鑹EDCap摰墧𧒄<EFBFBD>唳旿 + Dify<66><79>﹝<EFBFBD>亥<EFBFBD>摨?
<EFBFBD><EFBFBD> <20>怒<EFBFBD><E68092>ify<66>亥<EFBFBD>摨㯄<E691A8><E3AF84>琜<EFBFBD>2026-01-04摰峕<E691B0>嚗?
8.7 <20><><EFBFBD><EFBFBD>峕艶
摰峕<EFBFBD><EFBFBD>園𡢿: 2026-01-04
撘<EFBFBD><EFBFBD>穃極雿𣈯<EFBFBD>: 4-6撠𤩺𧒄
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>格<EFBFBD>: <20>沖EDCap摰墧𧒄<E5A2A7>唳旿<E594B3>亥砭<E4BAA5>箇<EFBFBD>銝𠺪<E98A9D>憓𧼮<E68693><F0A7BCAE>𠉛弦<F0A0899B>寞<EFBFBD><E5AF9E><EFBFBD>﹝<EFBFBD>亥砭<E4BAA5>賢<EFBFBD>
**<2A>詨<EFBFBD>隞瑕<E99A9E>?嚗?- <20><> <EFBFBD><EFBFBD>﹝<EFBFBD>亥砭: <20>亥砭<E4BAA5>𠉛弦<F0A0899B>寞<EFBFBD><E5AF9E><EFBFBD>RF銵冽聢<E586BD><E881A2>憐<EFBFBD><E68690><EFBFBD>隞?- <20><> **瘛瑕<E7989B>璉<EFBFBD>蝝?: <20>峕𧒄<E5B395>舀<EFBFBD>蝏𤘪<E89D8F><F0A498AA>𡝗㺭<F0A19D97>殷<EFBFBD>REDCap嚗匧<E59A97><E58CA7>䂿<EFBFBD><E482BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>﹝嚗㇄ify嚗?- <20>㴓 <EFBFBD>箄<EFBFBD>頝舐眏: <20>寞旿<E5AF9E>冽<EFBFBD><E586BD>桅<EFBFBD><E6A185>芸𢆡<E88AB8>㗇𥋘<E39787>唳旿皞?
8.8 <20><><EFBFBD>舀䲮獢?
<EFBFBD>寞<EFBFBD><EFBFBD>㗇𥋘
| 蝏游漲 | <EFBFBD><EFBFBD>鍂<EFBFBD>寞<EFBFBD> |
|---|---|
| *<EFBFBD>亥<EFBFBD>摨𤘪沲<EFBFBD>? | <EFBFBD>閖★<EFBFBD>桀<EFBFBD><EFBFBD>亥<EFBFBD>摨橒<EFBFBD>1銝杷IT憿寧𤌍 <20>?1銝枋ify Dataset嚗? |
| <EFBFBD><EFBFBD>﹝銝𠹺<EFBFBD> | Dify Web<65>屸𢒰<E5B1B8>见𢆡銝𠹺<E98A9D>嚗㇈VP<56>嗆挾嚗? |
| 憿寧𤌍<EFBFBD>唾<EFBFBD> | <EFBFBD>冽<EFBFBD>蝏穃<EFBFBD>暺䁅恕憿寧𤌍嚗<EFBFBD><EFBFBD><EFBFBD>典銁iit_schema.projects.dify_dataset_id嚗? |
<EFBFBD>詨<EFBFBD>摰䂿緵
1. <20>拙<EFBFBD><E68B99>誩㦛霂<E3A69B><E99C82>
<EFBFBD>灼ChatService.detectIntent()銝剜鰵憓裇query_protocol<EFBFBD>誩㦛嚗?
// 霂<><E99C82><EFBFBD><EFBFBD>﹝<EFBFBD>亥砭嚗<E7A0AD><E59A97>蝛嗆䲮獢<E4B2AE><E78DA2><EFBFBD>憐<EFBFBD><E68690><EFBFBD><EFBFBD>䰻<EFBFBD><E4B0BB><EFBFBD><EFBFBD>譌<EFBFBD><E8AD8C>RF蝑㚁<E89D91>
if (/(<28>𠉛弦<F0A0899B>寞<EFBFBD>|隡衣<E99AA1>|<7C>交<EFBFBD><E4BAA4>峕<EFBFBD>|CRF|<7C><><EFBFBD><EFBFBD>亙<EFBFBD>銵育蝥喳<E89DA5>|<7C>仿<EFBFBD>车<EFBFBD>㘾膄|<7C><><EFBFBD>|<7C>亦<EFBFBD><E4BAA6><EFBFBD><EFBFBD>|瘝餌<E7989D><E9A48C>寞<EFBFBD>|霂閖<E99C82>霈曇恣|<7C>𠉛弦<F0A0899B>桃<EFBFBD>|<7C>𠉛弦瘚<E5BCA6><E7989A>|閫<><E996AB><EFBFBD><EFBFBD><EFBFBD>|霂𦠜鱏<F0A6A09C><E9B18F><EFBFBD>|<7C>曄<EFBFBD><E69B84><EFBFBD><EFBFBD>)/.test(message)) {
return { intent: 'query_protocol' };
}
2. <20>啣<EFBFBD>Dify<66>亥砭<E4BAA5>寞<EFBFBD>
private async queryDifyKnowledge(query: string): Promise<string> {
// 1. <20>瑕<EFBFBD>憿寧𤌍<E5AFA7><F0A48C8D>ifyDatasetId
const project = await prisma.iitProject.findFirst({
where: { status: 'active' },
select: { name: true, difyDatasetId: true }
});
// 2. 靚<>鍂Dify API璉<49>蝝? const retrievalResult = await difyClient.retrieveKnowledge(
project.difyDatasetId,
query,
{ retrieval_model: { search_method: 'semantic_search', top_k: 5 } }
);
// 3. <20>澆<EFBFBD><E6BE86>𡝗<EFBFBD>蝝Y<E89D9D><EFBCB9>? // 靽桀<E99DBD>bug嚗帋蝙<E5B88B>冽迤蝖桃<E89D96>摮埈挾頝臬<E9A09D> record.segment.document.name <20>?record.segment.content
// ...
}
3. <20>湔鰵撖寡<E69296>瘚<EFBFBD><E7989A>
async handleMessage(userId: string, userMessage: string): Promise<string> {
const { intent, params } = this.detectIntent(userMessage);
// REDCap<61>亥砭
let toolResult: any = null;
if (intent === 'query_record') {
toolResult = await this.queryRedcapRecord(params.recordId);
}
// Dify<66>亥<EFBFBD>摨𤘪䰻霂? let difyKnowledge: string = '';
if (intent === 'query_protocol') {
difyKnowledge = await this.queryDifyKnowledge(userMessage);
}
// <20><>遣LLM瘨<4D><E798A8>嚗<EFBFBD><E59A97><EFBFBD>嗆釣<E59786>充EDCap<61>唳旿<E594B3>㷉ify<66>亥<EFBFBD>嚗? const messages = this.buildMessagesWithData(
userMessage, context, toolResult, difyKnowledge, userId
);
// 靚<>鍂LLM<4C><4D><EFBFBD><EFBFBD>䂿<EFBFBD>
const response = await this.llm.chat(messages);
// ...
}
8.9 <20>桅<EFBFBD><E6A185>埝䰻銝𦒘耨憭?
<EFBFBD>桅<EFBFBD>1: AI銝齿䰻霂¥ify嚗諹䌊撌梁<E6928C><E6A281>删<EFBFBD>獢?
<EFBFBD>啗情: <20>冽<EFBFBD><E586BD>?蝥喳<E89DA5><E596B3><EFBFBD><EFBFBD><EFBFBD>臭<EFBFBD>銋<EFBFBD><E98A8B>"嚗淾I蝻㚚<E89DBB>牐<EFBFBD>蝑娍<E89D91>嚗㷉ify<66>批<EFBFBD><E689B9>唳<EFBFBD><E594B3>亥砭霈啣<E99C88>
<EFBFBD>孵<EFBFBD>1: <20>誩㦛霂<E3A69B><E99C82><EFBFBD>喲睸霂滢<E99C82><E6BBA2>?- 蝻箏<EFBFBD>: "<22>仿<EFBFBD>?<3F>?霂𦠜鱏<F0A6A09C><E9B18F><EFBFBD>"<22>?<3F>曄<EFBFBD><E69B84><EFBFBD><EFBFBD>"
- 閫<EFBFBD><EFBFBD>: <20>拙<EFBFBD><E68B99>喲睸霂滚<E99C82>銵? <EFBFBD>孵<EFBFBD>2: Dify API餈𥪜<E9A488>摮埈挾頝臬<E9A09D><E887AC>躰秤
- <EFBFBD>躰秤:
record.document_name<EFBFBD><EFBFBD>record.content<20>?餈𥪜<E9A488>undefined` - 甇<EFBFBD>&:
record.segment.document.name<EFBFBD><EFBFBD>record.segment.content` - 閫<EFBFBD><EFBFBD>: 靽格迤摮埈挾霈輸䔮頝臬<E9A09D>
靚<EFBFBD><EFBFBD>餈<EFBFBD><EFBFBD>:
- <EFBFBD>𥕦遣
debug-dify-injection.ts餈質葵<EFBFBD>唳旿瘜典<EFBFBD>瘚<EFBFBD><EFBFBD> - <EFBFBD>𥕦遣
inspect-dify-response.ts<EFBFBD>亦<EFBFBD>Dify API摰鮋<E691B0>餈𥪜<E9A488>蝏𤘪<E89D8F> - <EFBFBD>𤑳緵撟嗡耨憭滚<EFBFBD>畾菔楝敺<EFBFBD><EFBFBD>霂?
8.10 瘚贝<E7989A>撉諹<E69289>
| 瘚贝<EFBFBD><EFBFBD>箸艶 | <EFBFBD>桅<EFBFBD> | <EFBFBD>唳旿皞? | 蝏𤘪<EFBFBD> |
|---|---|---|---|
| <EFBFBD><EFBFBD>﹝<EFBFBD>亥砭 | "餈嗘葵<E59798>𠉛弦<F0A0899B><E5BCA6><EFBFBD><EFBFBD>斗<EFBFBD><E69697><EFBFBD>糓隞<E7B393>銋<EFBFBD><E98A8B>" | Dify | <EFBFBD>?<3F>𣂼<EFBFBD> |
| CRF<EFBFBD>亥砭 | "CRF銵冽聢銝剜<E98A9D><E5899C>芯<EFBFBD>閫<EFBFBD><E996AB><EFBFBD><EFBFBD><EFBFBD>嚗? | Dify | <EFBFBD>?<3F>𣂼<EFBFBD> |
| *<EFBFBD><EFBFBD><EFBFBD><EFBFBD>䰻霂? | "ID 7<><37><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>? | REDCap | <EFBFBD>?<3F>𣂼<EFBFBD> |
| 蝏蠘恣<EFBFBD>亥砭 | "<22>桀<EFBFBD><E6A180>亦<EFBFBD>鈭<EFBFBD><E988AD>撠睲犖嚗? | REDCap | <EFBFBD>?<3F>𣂼<EFBFBD> |
| 瘛瑕<EFBFBD><EFBFBD>亥砭 | "餈嗘葵<E59798>𠉛弦<F0A0899B><E5BCA6>蜓閬<E89C93><E996AC>蝛嗥𤌍<E597A5><F0A48C8D>糓隞<E7B393>銋<EFBFBD><E98A8B>" | Dify | <EFBFBD>?<3F>𣂼<EFBFBD> |
8.11 <20><><EFBFBD><EFBFBD>鞉<EFBFBD>
**<2A><><EFBFBD>舀沲<E88880>?*:
<EFBFBD>冽<EFBFBD><EFBFBD>鞾䔮 <20>?<3F>誩㦛霂<E3A69B><E99C82> <20>?<3F>砂<EFBFBD> [query_protocol] <20>?Dify API <20>?<3F><>﹝<EFBFBD><EFB99D>挾
<20>鎿<EFBFBD> [query_record] <20>?REDCap API <20>?<3F><><EFBFBD><EFBFBD>㺭<EFBFBD>? <20>婙<EFBFBD> [count_records] <20>?REDCap API <20>?蝏蠘恣<E8A098>唳旿
<20>? <20><>遣LLM Prompt嚗𠄎ystem + Data + Context嚗? <20>? DeepSeek-V3
<20>? AI<41>䂿<EFBFBD>
<EFBFBD>詨<EFBFBD><EFBFBD>賢<EFBFBD>:
- <EFBFBD>?**瘛瑕<E7989B>璉<EFBFBD>蝝?*: <20>峕𧒄<E5B395>舀<EFBFBD>蝏𤘪<E89D8F><F0A498AA>𡝗㺭<F0A19D97>桀<EFBFBD><E6A180>䂿<EFBFBD><E482BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>﹝
- <EFBFBD>?<EFBFBD>箄<EFBFBD>頝舐眏: <20>寞旿<E5AF9E>誩㦛<E8AAA9>芸𢆡<E88AB8>㗇𥋘<E39787>唳旿皞?3. <20>?<EFBFBD>脫迫撟餉<EFBFBD>: <20><><EFBFBD>匧<EFBFBD>蝑𥪜抅鈭𡒊<E988AD>摰墧㺭<E5A2A7>?<3F><>﹝
- <EFBFBD>?<EFBFBD>交<EFBFBD><EFBFBD><EFBFBD>釣: 皜<>苊<EFBFBD><E88B8A>釣<EFBFBD>唳旿<E594B3>亥䌊REDCap<61>靝ify
霂衣<EFBFBD>霈啣<EFBFBD>: <20><><EFBFBD> [Dify<66>亥<EFBFBD>摨㯄<E691A8><E3AF84>𣂼<EFBFBD><F0A382BC>𤏸扇敶騟(../06-撘<><E69298>𤏸扇敶?2026-01-04-Dify<66>亥<EFBFBD>摨㯄<E691A8><E3AF84>𣂼<EFBFBD><F0A382BC>𤏸扇敶?md)
<EFBFBD>?銋腈<E98A8B><E88588><EFBFBD>餌<EFBFBD>
<EFBFBD>詨<EFBFBD><EFBFBD>𣂼停嚗<EFBFBD><EFBFBD>蝞<EFBFBD><EFBFBD>?+ Dify<66><79><EFBFBD>嚗?
- <EFBFBD>?**2憭拐<E686AD>蝥?*嚗𡁏<E59A97>敹怠<E695B9><E680A0>蚊I撖寡<E69296><E5AFA1>賢<EFBFBD>嚗<EFBFBD>鉄Dify<66><79><EFBFBD>嚗?2. <20>?**銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹?*嚗𡁏𣈲<F0A1818F><F0A388B2><EFBFBD>頧桀笆霂嘅<E99C82>3頧殷<E9A0A7>
- <EFBFBD>?**甇<>銁颲枏<E9A2B2><E69E8F>漤<EFBFBD>**嚗𡁻<E59A97><F0A181BB>滨鍂<E6BBA8>瑞<EFBFBD><E7919E>?4. <20>?**隞<><E99A9E>閫<EFBFBD><E996AB>**嚗?隞?<3F>質䌊<E8B3AA>刻<EFBFBD><E588BB>急<EFBFBD><E680A5>?5. <20>?瘛瑕<EFBFBD>璉<EFBFBD>蝝?*嚗𡁜<E59A97><F0A1819C>嗆𣈲<E59786><F0A388B2>EDCap摰墧𧒄<E5A2A7>唳旿 + Dify<66><79>﹝<EFBFBD>亥<EFBFBD>摨?6. <20>?<EFBFBD>脫迫撟餉<EFBFBD>**嚗𡁏<E59A97><F0A1818F>匧<EFBFBD>蝑𥪜抅鈭𡒊<E988AD>摰墧㺭<E5A2A7>殷<EFBFBD>蝏苷<E89D8F>蝻㚚<E89DBB>?
<EFBFBD><EFBFBD><EFBFBD>臭漁<EFBFBD>?
- <EFBFBD><EFBFBD> SessionMemory嚗𡁜<EFBFBD>摮睃<EFBFBD><EFBFBD>剁<EFBFBD><EFBFBD>𣳇<EFBFBD>Redis
- <EFBFBD>㴓 <EFBFBD>閙郊頝舐眏嚗帋<EFBFBD><EFBFBD>典<EFBFBD><EFBFBD><EFBFBD>eAct敺芰㴓
- <EFBFBD>圲 **憭滨鍂<E6BBA8>唳<EFBFBD>**嚗鑹edcapAdapter + WechatService
- <EFBFBD>?**<2A>扯<EFBFBD>靽肽<E99DBD>**嚗?3蝘垍垢<E59E8D>啁垢撱嗉<E692B1>
- <EFBFBD><EFBFBD> 摰∟恣摰峕㟲嚗朞扇敶閙<EFBFBD><EFBFBD>匧笆霂?
<EFBFBD>冽<EFBFBD>隞瑕<EFBFBD>?
**Before嚗㇄ay 3嚗?*嚗?- <20>?PI<50>臭誑<E887AD>交𤣰隡<F0A4A3B0><E99AA1>敺桐縑<E6A190>𡁶䰻
- <EFBFBD>?PI<50>䭾<EFBFBD>銝餃𢆡<E9A483>亥砭<E4BAA5>唳旿
- <EFBFBD>?<3F><>閬<EFBFBD>蒈敶𥵃EDCap<61>亦<EFBFBD>
**After嚗㇊hase 1.5 + Dify<66><79><EFBFBD>嚗?*嚗?- <20>?PI<50>臭誑<E887AD>其<EFBFBD>銝𡁜凝靽∩葉<E288A9>湔𦻖<E6B994>?<3F>亦<EFBFBD>憭𡁜<E686AD>鈭?嚗㇌EDCap嚗?- <20>?PI<50>臭誑<E887AD>?P001<30>劐<EFBFBD><E58A90>臬<EFBFBD>摨𥪜<E691A8>"嚗㇌EDCap嚗?- <20>?PI<50>臭誑<E887AD>?<3F>𠉛弦<F0A0899B><E5BCA6>熙<EFBFBD>交<EFBFBD><E4BAA4>斗<EFBFBD><E69697><EFBFBD>糓隞<E7B393>銋?嚗㇄ify嚗?- <20>?PI<50>臭誑<E887AD>?CRF銵冽聢銝剜<E98A9D><E5899C>芯<EFBFBD>閫<EFBFBD><E996AB><EFBFBD><EFBFBD><EFBFBD>"嚗㇄ify嚗?- <20>?AI霈啣<E99C88>銝𠹺<E98A9D>頧桀笆霂嘅<E99C82><E59885>舀<EFBFBD>隞<EFBFBD><E99A9E>
- <EFBFBD>?<3F>𧼮<EFBFBD>敹恍<E695B9><E6818D><EFBFBD><6蝘𡜐<E89D98>嚗峕<E59A97><E5B395>漤<EFBFBD>
- <EFBFBD>?AI<41>箔<EFBFBD><E7AE94>笔<EFBFBD><E7AC94>唳旿/<2F><>﹝<EFBFBD>䂿<EFBFBD>嚗䔶<E59A97>蝻㚚<E89DBB>?
<EFBFBD><EFBFBD> Phase 1.5 撘<><E69298>穃<EFBFBD><E7A983>鞉<EFBFBD>餌<EFBFBD> (2026-01-03 & 2026-01-04)
摰鮋<EFBFBD>摰峕<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <EFBFBD>?Day 1摰峕<E691B0> (2026-01-03): SessionMemory + ChatService + REDCap<61><70><EFBFBD>
- <EFBFBD>?Day 2摰峕<E691B0> (2026-01-04): Dify<66>亥<EFBFBD>摨㯄<E691A8><E3AF84>?+ 瘛瑕<E7989B>璉<EFBFBD>蝝?- <20>?瘚贝<EFBFBD><EFBFBD>朞<EFBFBD>: 隡<><E99AA1>敺桐縑撖寡<E69296> + <20>笔<EFBFBD><E7AC94>唳旿<E594B3>亥砭 + <20><>﹝<EFBFBD>亥砭
- <EFBFBD>?<EFBFBD>詨<EFBFBD>蝒<EFBFBD>聦: 閫<><E996AB>LLM撟餉<E6929F><E9A489>桅<EFBFBD> + 瘛瑕<E7989B>璉<EFBFBD>蝝X沲<EFBCB8>?
<EFBFBD>喲睸<EFBFBD>鞉<EFBFBD>
- <EFBFBD>?AI<41>箔<EFBFBD>REDCap<61>笔<EFBFBD><E7AC94>唳旿<E594B3>䂿<EFBFBD>嚗䔶<E59A97>蝻㚚<E89DBB>?2. <20>?AI<41>箔<EFBFBD>Dify<66>亥<EFBFBD>摨𤘪<E691A8>獢<EFBFBD><E78DA2>蝑𠉛<E89D91>蝛嗆䲮獢<E4B2AE>䔮憸?3. <20>?瘛瑕<E7989B>璉<EFBFBD>蝝g<E89D9D><EFBD87>峕𧒄<E5B395>舀<EFBFBD>蝏𤘪<E89D8F><F0A498AA>𡝗㺭<F0A19D97>桀<EFBFBD><E6A180>䂿<EFBFBD><E482BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>﹝
- <EFBFBD>?隞擧㺭<E693A7>桀<EFBFBD>霂餃<E99C82>憿寧𤌍<E5AFA7>滨蔭嚗ōest0102嚗?5. <20>?<3F>誩㦛霂<E3A69B><E99C82> + <20>箄<EFBFBD>頝舐眏 + <20>唳旿<E594B3>亥砭 + LLM<4C><4D><EFBFBD>
- <EFBFBD>?銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹<E68987><E695B9><EFBFBD><EFBFBD>餈?頧桀笆霂嘅<E99C82>
- <EFBFBD>?<3F>單𧒄<E596AE>漤<EFBFBD>嚗?甇<>銁<EFBFBD>亥砭"嚗?
瘚贝<EFBFBD>撉諹<EFBFBD>
- 憿寧𤌍: test0102
- REDCap PID: 16, 11<31>∟扇敶? - Dify Dataset ID:
b49595b2-bf71-4e47-9988-4aa2816d3c6f - <EFBFBD><EFBFBD>﹝: <20>𠉛弦<F0A0899B>寞<EFBFBD><E5AF9E><EFBFBD>RF銵冽聢嚗?銝芣<E98A9D>隞塚<E99A9E>撌脣<E6928C><E884A3><EFBFBD><EFBFBD>
- REDCap PID: 16, 11<31>∟扇敶? - Dify Dataset ID:
- <EFBFBD>箸艶1: <20>亥砭ID 7<><37><EFBFBD><EFBFBD>縑<EFBFBD>荔<EFBFBD>REDCap嚗争<E59A97> <20>?摰<><E691B0><EFBFBD>寥<EFBFBD><E5AFA5>笔<EFBFBD><E7AC94>唳旿
- <EFBFBD>箸艶2: <20>亥砭<E4BAA5>𠉛弦<F0A0899B>㘾膄<E398BE><E88684><EFBFBD>嚗㇄ify嚗争<E59A97> <20>?<3F>箔<EFBFBD><E7AE94><EFBFBD>﹝<EFBFBD><EFB99D>&<EFBFBD>䂿<EFBFBD>
- <EFBFBD>箸艶3: <20>亥砭CRF閫<46><E996AB><EFBFBD><EFBFBD><EFBFBD>嚗㇄ify嚗争<E59A97> <20>?<3F>箔<EFBFBD><E7AE94><EFBFBD>﹝<EFBFBD><EFB99D>&<EFBFBD>䂿<EFBFBD>
- <EFBFBD>箸艶4: 蝏蠘恣<E8A098>亦<EFBFBD>鈭箸㺭嚗㇌EDCap嚗争<E59A97> <20>?<3F><>&蝏蠘恣11鈭?- 蝏𤘪<EFBFBD>: <20>?<3F><><EFBFBD>㗇<EFBFBD>霂閖<E99C82>朞<EFBFBD>嚗峕<E59A97>蝻㚚<E89DBB>?
霂衣<EFBFBD>霈啣<EFBFBD>
- Phase 1.5撘<EFBFBD><EFBFBD>穃<EFBFBD><EFBFBD>鞱扇敶?(REDCap<61><70><EFBFBD>)
- [Dify<66>亥<EFBFBD>摨㯄<E691A8><E3AF84>𣂼<EFBFBD><F0A382BC>𤏸扇敶騟(../06-撘<><E69298>𤏸扇敶?2026-01-04-Dify<66>亥<EFBFBD>摨㯄<E691A8><E3AF84>𣂼<EFBFBD><F0A382BC>𤏸扇敶?md)
**蝏湔擪<E6B994>?*嚗䥑IT Manager撘<72><E69298>穃𣪧<E7A983>?
**<2A><><EFBFBD>擧凒<E693A7>?*嚗?026-01-03
**<2A><>﹝<EFBFBD>嗆<EFBFBD>?*嚗尠<E59A97> Phase 1.5撌脣<EFBFBD><EFBFBD>?