Files
AIclinicalresearch/docs/03-业务模块/IIT Manager Agent/04-开发计划/Phase1.5-AI对话能力开发计划.md
HaHafeng 1b53ab9d52 feat(aia): Complete AIA V2.0 with universal streaming capabilities
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%)
2026-01-14 19:15:01 +08:00

2749 lines
83 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# IIT Manager Agent - Phase 1.5 AI撖寡<E69296><E5AFA1><EFBFBD><EFBFBD><E69298>𤏸恣<F0A48FB8>?
> **<2A><>𧋦**: v3.0嚗<EFBFBD><EFBFBD><EFBFBD><EFBFBD>?+ 銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹?+ Dify<66><EFBFBD>摨橒<E691A8>
> **<2A>𥕦遣<F0A595A6><EFBFBD>**: 2026-01-03
> **<2A><><EFBFBD>唳凒<E594B3>?*: 2026-01-04
> **<2A><EFBFBD>?*: <20>?**撌脣<E6928C><E884A3><EFBFBD><E7909C>非ify<66><79><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>
> **<2A><EFBFBD><E8A9A8>𣂼停**: <20>?REDCap<61>唳旿<E594B3><E697BF><EFBFBD> + <20>?銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹?+ <20>?閫<><E996AB>LLM撟餉<E6929F> + <20>?**Dify<66><EFBFBD>摨𤘪毽<F0A498AA><E6AFBD><EFBFBD>蝝?*
---
## <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>笔鍳<E7AC94><EFBFBD>1憭拐<E686AD>蝥選<E89DA5><E981B8>?**<2A>𡁶鍂<F0A181B6><EFBFBD><EFBFBD><E69285><EFBFBD><EFBFBD><EFBFBD>**
### <20><> <20>滚之<E6BB9A>𤑳緵嚗𡁻<E59A97>𡁶鍂<F0A181B6><EFBFBD><EFBFBD>歇摰<E6AD87><E691B0>嚗?
**撟喳蝱<E596B3>啁𠶖**嚗?026-01-03靚<33><E99D9A>蝏𤘪<E89D8F>嚗㚁<E59A97>
- <20>?**LLMFactory 摰<><E691B0>撠梁貌**嚗?蝘齿芋<E9BDBF><EFBFBD>DeepSeek/Qwen/GPT-5/Claude嚗㚁<E59A97><E39A81><EFBFBD><E79285>嚗屸妟<E5B1B8>滨蔭
- <20>?**ChatContainer 摰<><E691B0>撠梁貌**嚗鋫nt Design X蝏<58>辣嚗<E8BEA3><EFBFBD>汽ool C撉諹<E69289>嚗ǚ968銵䕘<E98AB5>
- <20>?**<2A><EFBFBD><E887AC><EFBFBD>撌脤<E6928C>蝵?*嚗䫤DEEPSEEK_API_KEY`<60><>QWEN_API_KEY`蝑?- <20>?**<2A><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>
- <20>𥕦遣ChatService.ts嚗?撠𤩺𧒄嚗? - <20>𥕦遣SessionMemory.ts嚗?撠𤩺𧒄嚗? - 靽格㺿WechatCallbackController嚗?撠𤩺𧒄嚗?<3F>?Day 2嚗?-6撠𤩺𧒄嚗? Dify<66><EFBFBD>摨㯄<E691A8><E3AF84>?+ 瘛瑕<E7989B><EFBFBD><E89D9D>2026-01-04摰峕<E691B0>嚗? - <20><EFBFBD>憿寧𤌍銝𥟠ify<66><EFBFBD>摨橒<E691A8>1撠𤩺𧒄嚗? - <20><><EFBFBD>Dify璉<79><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>嚗?```
<EFBFBD>?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><> **銝餃𢆡<E9A483>亥砭**嚗䥪I銝滨鍂蝑厰<E89D91>𡁶䰻嚗屸<E59A97><E5B1B8>園䔮"<22>啣銁<E595A3><EFBFBD>憭𡁜<E686AD>鈭綽<E988AD>"
- <20><> **<2A>唳旿蝛輸<E89D9B>?*嚗𡁜<E59A97><F0A1819C>嗆䰻霂㎞EDCap<61>唳旿嚗<E697BF><E59A97><EFBFBD><EFBFBD><EFBFBD><E7A595><EFBFBD><EFBFBD><EFBFBD>抒𠶖<E68A92><F0A0B696><EFBFBD>
- <20><> **<2A><EFBFBD><EFBFBD>蝝?*嚗𡁏䰻霂<E99C82>蝛嗆䲮獢<E4B2AE><E78DA2><EFBFBD>RF銵冽聢<E586BD><E881A2><EFBFBD><EFBFBD><EFBFBD><E59F9D>?- <20>働 **<2A><EFBFBD><E7AE84><EFBFBD>圾**嚗朞䌊<E69C9E>嗉祗閮<E7A597><E996AE>鞾䔮嚗峕<E59A97><E5B395><EFBFBD>霈啣<E99C88><E595A3>賭誘
---
## <20><> 鈭䎚<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>蝑?
| <20><EFBFBD><E5969F>?| <20><EFBFBD> | <20><EFBFBD> |
|--------|------|------|
| **AI<41><EFBFBD>撘閙<E69298>** | DeepSeek-V3 (API) | <20>找遠瘥娪<E798A5>嚗峕𣈲<E5B395><F0A388B2>unction Calling |
| **<2A><EFBFBD>摨?* | Dify<66>砍𧑐Docker | 撌脤<E6928C>蝵莎<E89DB5><E88E8E>𣳇<EFBFBD>憭𡝗<E686AD><F0A19D97>穿<EFBFBD>撱嗉<E692B1>雿?|
| **<2A><EFBFBD><E785BE>唳旿摨?* | Dify<66><79>蔭Weaviate | <20>滨輕<E6BBA8><EFBFBD><EFBFBD>蝞勗朖<E58B97>?|
| **頝舐眏蝑𣇉裦** | <20>閙郊<E99699>誩㦛霂<E3A69B><E99C82> | MVP<56>嗆挾蝞<E68CBE><E89D9E><EFBFBD>銝滨鍂ReAct敺芰㴓 |
| **<2A>唳旿<E594B3>亥砭** | RedcapAdapter | 撌脫<E6928C>嚗𣬚凒<F0A3AC9A><EFBFBD><E4BA99>?|
---
## <20>?鈭䎚<E988AD><E48E9A><EFBFBD><EFBFBD><E89D9E><EFBFBD><EFBFBD><EFBFBD>𤏸恣<F0A48FB8>𡜐<EFBFBD>2憭抬<E686AD>
### <20>㴓 Day 1嚗𡁜抅蝖<E68A85>撖寡<E69296><E5AFA1><EFBFBD>嚗?撠𤩺𧒄嚗?
#### <20><EFBFBD><E8A9A8><EFBFBD>
**霈呸I<E591B8><EFBFBD>蝑𠉛鍂<F0A0899B>琿䔮憸矋<E686B8><E79F8B>芣䰻REDCap<61>唳旿嚗?*
#### 隞餃𦛚1.1嚗𡁜<EFBFBD>撱搴essionMemory嚗?0<><30><EFBFBD>嚗?
**<2A><>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/agents/SessionMemory.ts`
```typescript
/**
* 隡朞<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>
- <20>?<3F>芸𢆡皜<F0A286A1><E79A9C><EFBFBD><E9A488>隡朞<E99AA1>
---
#### 隞餃𦛚1.2嚗𡁜<EFBFBD>撱慢hatService嚗?撠𤩺𧒄嚗争黾 憭滨鍂LLMFactory
**<EFBFBD><EFBFBD>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/services/ChatService.ts`
```typescript
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
? `<EFBFBD>𣂷<EFBFBD>銝𧢲<EFBFBD><EFBFBD>髢n${context}\n\n<>𣂼<EFBFBD><F0A382BC>漤䔮憸塩<E686B8>髢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>賣䰻霂<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 = ['隞?, '憟?, '餈嗘葵<E59798><E891B5><EFBFBD>?, '霂交<E99C82><E4BAA4>?, '餈嗘<E9A488>', '<27><><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>急䰻霂<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撠𤩺𧒄嚗?
**<2A><>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/agents/SimpleToolExecutor.ts`
```typescript
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>䰻霂<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>舀䰻霂<EFBD81><EFBFBD>霈?- <20>?<3F>舀䰻霂<E99C82><EFBCB8><EFBFBD><EFBFBD>?- <20>?<3F>舀䰻霂<EFBCBA>抒𠶖<E68A92>?
---
#### 隞餃𦛚1.4嚗𡁶<EFBFBD><EFBFBD>䨝nswerGenerator嚗?撠𤩺𧒄嚗?
**<2A><>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/agents/SimpleAnswerGenerator.ts`
```typescript
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 `<EFBFBD><EFBFBD> **${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 `<EFBFBD>𪈠 **<EFBFBD><EFBFBD><EFBFBD>?${data.patientId} 霂行<E99C82>**
<EFBFBD><EFBFBD> **<EFBFBD>箸𧋦靽⊥<EFBFBD>**嚗?- 撟湧<E6929F>嚗?{details.age || '<27><EFBFBD><E88AB8>?}撗?- <20><EFBFBD>嚗?{details.gender || '<27><EFBFBD><E88AB8>?}
- BMI嚗?{details.bmi || '<27><EFBFBD><E88AB8>?}
<EFBFBD><EFBFBD> **敶訫<E695B6><E8A8AB><EFBFBD>?*嚗?- ${details.complete}
<EFBFBD><20><><EFBFBD>擧凒<E693A7><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> **韐冽綉<E586BD><EFBFBD>?*\n\n`;
answer += `<EFBFBD>𩤃<EFBFBD> **韐冽綉<E586BD><EFBFBD><E6A185>?*嚗?{data.issueCount}銝歿n\n`;
if (issues.length > 0) {
answer += `<EFBFBD><EFBFBD> **<EFBFBD><EFBFBD>餈煾䔮憸?*嚗䨵n`;
issues.forEach((issue: any, index: number) => {
answer += `${index + 1}. 霈啣<E99C88>${issue.recordId}\n`;
});
} else {
answer += `<EFBFBD>?<3F><><EFBFBD>韐冽綉<E586BD><EFBFBD>`;
}
return answer;
}
/**
* <20><><EFBFBD><EFBFBD>躰秤<E8BAB0>鞟內
*/
private generateErrorMessage(error?: string): string {
return `<EFBFBD>?<3F>亥砭憭梯揖
<EFBFBD><EFBFBD>嚗?{error || '<27>芰䰻<E88AB0>躰秤'}
<EFBFBD><20>典虾隞伐<E99A9E>
1. 蝔滚<E89D94><E6BB9A><EFBFBD>
2. <20><EFBCB6><EFBFBD>
3. <20>𠉛頂蝞∠<E89D9E><E288A0>𧜶;
}
}
```
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F><EFBFBD><E482BF><EFBFBD><E6BE86>见末
- <20>?<3F><EFBFBD>Markdown
- <20>?<3F>躰秤<E8BAB0>鞟內皜<E585A7>
---
#### 隞餃𦛚1.5嚗𡁻<EFBFBD><EFBFBD>𣂼<EFBFBD>WechatCallbackController嚗?撠𤩺𧒄嚗?
**靽格㺿<E6A0BC><E3BABF>辣**嚗䫤backend/src/modules/iit-manager/controllers/WechatCallbackController.ts`
**<2A>玖andleCallback<63><EFBFBD>銝剜溶<E5899C>?*嚗?
```typescript
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('<27>?憭<><E686AD><EFBFBD><EFBFBD><EFBFBD><E798A8>憭梯揖', {
error: error.message
});
await wechatService.sendTextMessage(
userId,
'<27><EFBFBD>嚗峕<E59A97><E5B395><EFBFBD><EFBFBD><EFBFBD><E988AD>鈭偦䔮憸矋<E686B8>霂瑞<E99C82><E7919E>𤾸<EFBFBD>霂?
);
}
});
}
}
```
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>舀𦻖<E88880>嗥鍂<E597A5><EFBFBD><E7919F>?- <20>?蝡见朖<E8A781><EFBFBD>?甇<><EFBFBD>亥砭..."
- <20>?甇<><EFBC86><E99C82><EFBFBD>誩㦛
- <20>?甇<><EFBFBD><EFBFBD>撌亙<E6928C>
- <20>?甇<><EFBFBD><EFBFBD><E785BE><EFBFBD>憭?- <20>?銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹<E68987><E695B9><EFBFBD>?
---
### <20>㴓 Day 2嚗帋<E59A97>銝𧢲<E98A9D>隡睃<E99AA1> + 瘚贝<E7989A>嚗?撠𤩺𧒄嚗?
#### 隞餃𦛚2.1嚗帋<EFBFBD>銝𧢲<EFBFBD>霈啣<EFBFBD>隡睃<EFBFBD>嚗?撠𤩺𧒄嚗?
**憓𧼮撩SessionMemory嚗峕𣈲<E5B395><F0A388B2><EFBFBD><EFBFBD><EFBFBD>D<EFBFBD>𣂼<EFBFBD>**嚗?
```typescript
// <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;
}
```
**<EFBFBD>沒impleIntentRouter銝凋蝙<EFBFBD>?*嚗?
```typescript
// 憒<><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撠𤩺𧒄嚗?
**瘚贝<E7989A><E8B49D>箸艶**嚗?
```typescript
// <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>?
}
```
**瘚贝<E7989A>甇仿炊**嚗?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>
- <20>?"甇<>銁颲枏<E9A2B2>"<22><EFBFBD><E6BCA4><EFBFBD><EFBFBD>
- <20>?銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹<E68987><E695B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E99A9E><EFBFBD><E996AB>嚗?- <20>?<3F>𧼮<EFBFBD><F0A7BCAE>園𡢿<3蝘?
---
### <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?
| <20><EFBFBD> | 撉峕𤣰<E5B395><F0A4A3B0><EFBFBD> | 隡睃<E99AA1>蝥?|
|------|---------|-------|
| **<EFBFBD><EFBFBD>撖寡<EFBFBD>** | <20>舀䰻霂㎞EDCap<61>唳旿 | <20>𣞁 P0 |
| **銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹?* | <20><EFBFBD><E88880><EFBFBD>餈?頧桀笆霂?| <20>𣞁 P0 |
| **隞<><E99A9E><EFBFBD><E996AB>** | "隞?<3F>質䌊<E8B3AA><EFBFBD><E588BB><EFBFBD><E680A5>?| <20>𣞁 P0 |
| **甇<>銁颲枏<E9A2B2><E69E8F><EFBFBD>** | 蝡见朖<E8A781>?甇<><EFBFBD>亥砭..." | <20>𣞁 P0 |
| **<EFBFBD>𧼮<EFBFBD>撱嗉<EFBFBD>** | <3蝘?| <20>𣞁 P0 |
| **<EFBFBD>誩㦛霂<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?* | >80% | <20>𣞁 P0 |
---
### <20><> <20><><EFBFBD><EFBFBD>ǒs摰峕㟲<E5B395><E39FB2>笆瘥?
| <20><EFBFBD> | <20><><EFBFBD><EFBFBD>?(2憭? | 摰峕㟲<E5B395>?(5憭? |
|------|------------|------------|
| REDCap<61>亥砭 | <20>?| <20>?|
| 銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹?| <20>?(<28><><EFBFBD>3頧? | <20>?(<28><><EFBFBD>3頧? |
| 甇<>銁颲枏<E9A2B2><E69E8F><EFBFBD> | <20>?| <20>?|
| Dify<66><EFBFBD>摨?| <20>?| <20>?|
| <20>冽𥁒<E586BD>芸𢆡敶埝﹝ | <20>?| <20>?|
| <20><><EFBFBD>亥砭 | <20>?| <20>?|
---
## <20><>儭?銝剹<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<66>𤾸蝱 <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><E89D9D><EFBCB8>?*
```
瘚贝<E7989A><E8B49D><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>?璉<><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>
```bash
# 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
- <20>?<3F><EFBFBD><E88880><EFBFBD>隡䭾<E99AA1>獢?- <20>?<3F>躰秤憭<E7A7A4><E686AD><EFBFBD><E691B0>
---
#### 隞餃𦛚1.4嚗𡁶<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>霂𤏪<EFBFBD>2撠𤩺𧒄嚗?
**<2A><>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/adapters/__tests__/DifyAdapter.test.ts`
```typescript
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%
- <20>?<3F><><EFBFBD><EFBFBD>霂閧鍂靘钅<E99D98><EFBFBD>
---
### Day 2嚗𡁏<E59A97><F0A1818F><EFBFBD><E69B87><EFBFBD>頝舐眏<E88890><EFBFBD>嚗?撠𤩺𧒄嚗?
#### 隞餃𦛚2.1嚗朞挽霈<EFBFBD><EFBFBD>銋㚁<EFBFBD>Tool Schema嚗㚁<E59A97>2撠𤩺𧒄嚗?
**<2A><>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/agents/tools.ts`
```typescript
/**
* IIT Manager Agent撌亙<E6928C>摰帋<E691B0>
*/
export const iitAgentTools = [
// 撌亙<E6928C>1嚗𡁏䰻霂<E99C82><EFBCB7>嗆㺭<E59786>? {
type: "function",
function: {
name: "query_clinical_data",
description: `<EFBFBD>鞉䰻REDCap摰墧𧒄<EFBFBD>唳旿<EFBFBD>𤑳鍂鈭擧䰻霂葩摨羓<EFBFBD>蝛嗥<EFBFBD>摰墧𧒄<EFBFBD>唳旿<EFBFBD><EFBFBD><EFBFBD><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>䰻霂<E4B0BB><E99C82>
{
type: "function",
function: {
name: "search_knowledge_base",
description: `<EFBFBD>鞉䰻<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>箸艶嚗?- <20><EFBFBD>蝛嗉<E89D9B><E59789><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E4BAA4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E98A8B><EFBFBD>𠉛弦蝏<E5BCA6><E89D8F><EFBFBD>𦒘<EFBFBD>摰帋<E691B0>嚗?- <20>哽RF銵冽聢嚗𡁏<E59A97>銝芸<E98A9D>畾萇<E795BE>摰帋<E691B0><E5B88B><EFBFBD><EFBFBD><E98A8B>憛怠<E6869B><EFBFBD><E996AB><EFBFBD><EFBFBD>
- <20><EFBFBD><E6A180>脰扇敶𤏪<E695B6>銝𠰴𪂹<F0A0B0B4><F0AA82B9>𪂹<EFBFBD>仿<EFBFBD><E4BBBF>𣂼<EFBFBD><EFBFBD><E988AD><EFBFBD>䔮憸矋<E686B8>`,
parameters: {
type: "object",
properties: {
query: {
type: "string",
description: "<22>𦦵揣<F0A6A6B5>喲睸霂齿<E99C82><E9BDBF><EFBFBD>"
},
doc_category: {
type: "string",
enum: ["protocol", "crf", "report"],
description: `<60><>﹝蝐餃<E89D90>嚗?- protocol: <20>𠉛弦<F0A0899B><EFBFBD><E5AF9E><EFBFBD><EFBFBD><E68690>鸌隞嗚<E99A9E><E5979A><EFBFBD><E4B0BB><EFBFBD><EFBFBD>譍髡<E8AD8D><E9ABA1><EFBFBD><EFBFBD><EFBFBD><E59F9D>?- crf: CRF銵冽聢摰帋<E691B0><E5B88B><EFBFBD><EFBFBD>躰秩<E8BAB0>汿<EFBFBD><E6B1BF><EFBFBD><EFBFBD><E6A180>?- report: 憿寧𤌍<E5AFA7>冽𥁒<E586BD><F0A58192><EFBFBD>摨行<E691A8><EFBFBD><E9A48C><EFBFBD><EFBFBD><EFBFBD>脰扇敶𧄧
}
},
required: ["query"]
}
}
}
];
```
---
#### 隞餃𦛚2.2嚗𡁜<EFBFBD><EFBFBD><EFBFBD><EFBFBD>曇楝<EFBFBD>勗膥嚗㇆ntent Router嚗㚁<E59A97>3撠𤩺𧒄嚗?
**<2A><>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/agents/IntentRouter.ts`
```typescript
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>誩㦛
- <20>?<3F>舀迤蝖株<E89D96><E6A0AA>急䰻<E680A5><E4B0BB><EFBFBD>誩㦛
- <20>?<3F><EFBFBD><E887AC><EFBFBD>𤦭<EFBFBD>𠰴㦤<F0A0B0B4>?
---
#### 隞餃𦛚2.3嚗𡁜<EFBFBD><EFBFBD>啣極<EFBFBD><EFBFBD><EFBFBD>膥嚗㇍ool Executor嚗㚁<E59A97>3撠𤩺𧒄嚗?
**<2A><>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/agents/ToolExecutor.ts`
```typescript
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>嚗𡁏䰻霂葩摨𦠜㺭<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: '蝻箏<E89DBB><E7AE8F><EFBFBD><EFBFBD><EFBFBD>D'
};
}
const records = await redcap.exportRecords([args.patient_id]);
if (records.length === 0) {
return {
success: false,
data: null,
error: `<EFBFBD>芣𪄳<EFBFBD><EFBFBD><EFBFBD>?${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>䰻霂<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 || '<27><EFBFBD>摨𤘪<E691A8>仃韐?
};
}
// 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嚗?撠𤩺𧒄嚗?
**靽格㺿<E6A0BC><E3BABF>辣**嚗䫤backend/src/modules/iit-manager/controllers/WechatCallbackController.ts`
**<EFBFBD>函緵<EFBFBD><EFBFBD>`handleCallback`<EFBFBD><EFBFBD>銝剖<EFBFBD><EFBFBD>鼦I撖寡<EFBFBD><EFBFBD><EFBFBD>**嚗?
```typescript
// <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撠𤩺𧒄嚗?
**<2A><>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/agents/AnswerGenerator.ts`
```typescript
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><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 `<EFBFBD><EFBFBD> **憿寧𤌍蝏蠘恣<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 `<EFBFBD>𪈠 **<EFBFBD><EFBFBD><EFBFBD>?${data.patientId} 霂行<E99C82>**
<EFBFBD><EFBFBD> **<EFBFBD>箸𧋦靽⊥<EFBFBD>**嚗?- 撟湧<E6929F>嚗?{details.age || '<27><EFBFBD><E88AB8>?}撗?- <20><EFBFBD>嚗?{details.gender || '<27><EFBFBD><E88AB8>?}
- BMI嚗?{details.bmi || '<27><EFBFBD><E88AB8>?}
<EFBFBD><EFBFBD> **敶訫<E695B6><E8A8AB><EFBFBD>?*嚗?- <20>唳旿摰峕㟲摨佗<E691A8>${details.complete === '2' ? '<27>?撌脣<E6928C><E884A3>? : '<27>?餈𥡝<E9A488>銝?}
<EFBFBD><20><><EFBFBD>擧凒<E693A7><EFBFBD>${new Date().toLocaleString('zh-CN')}`;
}
if (type === 'qc_status') {
const issues = data.recentIssues.slice(0, 5);
let answer = `<EFBFBD><EFBFBD> **韐冽綉<E586BD><EFBFBD>?*\n\n`;
answer += `<EFBFBD>𩤃<EFBFBD> **韐冽綉<E586BD><EFBFBD><E6A185>?*嚗?{data.issueCount}銝歿n\n`;
if (issues.length > 0) {
answer += `<EFBFBD><EFBFBD> **<EFBFBD><EFBFBD>餈煾䔮憸?*嚗䨵n`;
issues.forEach((issue: any, index: number) => {
answer += `${index + 1}. 霈啣<E99C88>${issue.recordId}嚗?{JSON.stringify(issue.issue).substring(0, 50)}\n`;
});
} else {
answer += `<EFBFBD>?<3F><><EFBFBD>韐冽綉<E586BD><EFBFBD>`;
}
return answer;
}
return JSON.stringify(data, null, 2);
}
/**
* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><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>?<3F>芣𪄳<E88AA3>啁㮾<E59581><EFBFBD>摰?
<EFBFBD>働 撱箄悅嚗?1. 撠肽<E692A0><E882BD><EFBCB6>喲睸霂?2. <20><EFBFBD><E4BAA6>𠉛弦<F0A0899B><EFBFBD><E5AF9E><EFBFBD><EFBFBD>
3. <20>𠉛頂憿寧𤌍<E5AFA7><EFBFBD><E8AE9B>𧜶;
}
// 撠<><E692A0><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><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璉<6E><E89D9D><EFBCB7><EFBFBD><EFBFBD><EFBFBD><E78DA2>摰對<E691B0>\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>摨𤘪䰻霂<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><EFBCB6><EFBFBD>
3. <20>𠉛頂蝞∠<E89D9E><E288A0>𧜶;
}
}
```
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F><EFBFBD><E88890>鞉㺭<E99E89>格䰻霂<E99C82>蝑?- <20>?<3F><EFBFBD><E88890>鞟䰻霂<E4B0BB><E99C82><E89D9D>蝑?- <20>?<3F><EFBFBD><E482BF><EFBFBD><E6BE86>见末嚗㇈arkdown嚗?- <20>?<3F>躰秤<E8BAB0>鞟內皜<E585A7>
---
#### 隞餃𦛚3.3嚗𡁶垢<EFBFBD>啁垢瘚贝<EFBFBD>嚗?撠𤩺𧒄嚗?
**瘚贝<E7989A><E8B49D>箸艶**嚗?
```typescript
// 瘚贝<E7989A><E8B49D>箸艶1嚗𡁏䰻霂<EFBD81><EFBFBD>霈?{
input: "<22>啣銁<E595A3><EFBFBD>憭𡁜<E686AD>鈭箔<E988AD>嚗?,
expectedTool: "query_clinical_data",
expectedIntent: "project_stats",
expectedOutput: "<22><> 憿寧𤌍蝏蠘恣<E8A098>唳旿\n<>?<3F><EFBFBD>鈭箸㺭嚗䧥X靘?
}
// 瘚贝<E7989A><E8B49D>箸艶2嚗𡁏䰻霂鸌摰𡁏<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嚗𡁏䰻霂<E99C82>蝛嗆䲮獢?{
input: "<22><EFBFBD><E4BAA4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E98A8B>",
expectedTool: "search_knowledge_base",
expectedCategory: "protocol",
expectedOutput: "<22><> <20><EFBFBD>摨𤘪䰻霂<E99C82><EFBCB9>?
}
// 瘚贝<E7989A><E8B49D>箸艶4嚗𡁏䰻霂RF銵冽聢
{
input: "BMI餈嗘葵摮埈挾<E59F88>𦒘<EFBFBD>憛恬<E6869B>",
expectedTool: "search_knowledge_base",
expectedCategory: "crf",
expectedOutput: "<22><> <20><EFBFBD>摨𤘪䰻霂<E99C82><EFBCB9>?
}
// 瘚贝<E7989A><E8B49D>箸艶5嚗𡁻𤦭<F0A181BB>?{
input: "雿惩末",
expectedTool: null,
expectedOutput: "<22>典末嚗<E69CAB><E59A97><EFBFBD>臭葩摨羓<E691A8>蝛嗅𨭌<E59785>?
}
```
**瘚贝<E7989A>甇仿炊**嚗?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>
- <20>?<3F>𧼮<EFBFBD><F0A7BCAE>園𡢿<3蝘?- <20>?<3F>𧼮<EFBFBD><F0A7BCAE><EFBFBD><EFBFBD><E68D86>
- <20>?摰∟恣<E2889F><EFBFBD>摰峕㟲
---
### Day 4嚗𡁜𪂹<F0A1819C>亥䌊<E4BAA5><EFBFBD><EFBFBD><E78DA2>6撠𤩺𧒄嚗?
#### 隞餃𦛚4.1嚗𡁜<EFBFBD><EFBFBD>啣𪂹<EFBFBD><EFBFBD><EFBFBD>𣂼膥嚗?撠𤩺𧒄嚗?
**<2A><>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/services/WeeklyReportGenerator.ts`
```typescript
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
- <20>?<3F><EFBFBD>摮睃<E691AE><E79D83>唳旿摨?- <20>?<3F><EFBFBD>隡惩<E99AA1>Dify
- <20>?<3F>脫迫<E884AB><EFBFBD><E6BB9A><EFBFBD><EFBFBD>
---
#### 隞餃𦛚4.2嚗𡁻<EFBFBD>蝵桀<EFBFBD><EFBFBD>嗡遙<EFBFBD><EFBFBD>2撠𤩺𧒄嚗?
**<2A><>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/index.ts`
```typescript
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('<27>?<3F>冽𥁒<E586BD><F0A58192><EFBFBD>摰峕<E691B0>', {
projectCount: projects.length
});
} catch (error: any) {
logger.error('<27>?<3F>冽𥁒<E586BD><F0A58192><EFBFBD>憭梯揖', {
error: error.message
});
}
}, {
timezone: 'Asia/Shanghai'
});
logger.info('<27>?<3F>冽𥁒摰𡁏𧒄隞餃𦛚撌脫釣<E884AB><EFBFBD>瘥誩𪂹銝<F0AA82B9> 00:00嚗?);
}
```
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?摰𡁏𧒄隞餃𦛚瘜典<E7989C><E585B8>𣂼<EFBFBD>
- <20>?<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撠𤩺𧒄嚗?
**<2A><>辣雿滨蔭**嚗䫤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>䰻霂<EFBCB9>?3. 撣貊鍂<E8B28A><EFBFBD>蝷箔<E89DB7>
4. 瘜冽<E7989C>鈭钅★
5. 撣貉<E692A3><E8B289><EFBFBD>FAQ
---
#### 隞餃𦛚5.2嚗䥪hase 1.5撘<EFBFBD><EFBFBD>𤏸扇敶𤏪<EFBFBD>2撠𤩺𧒄嚗?
**<2A><>辣雿滨蔭**嚗䫤AIclinicalresearch/docs/03-銝𡁜𦛚璅<E79285>/IIT Manager Agent/06-撘<><E69298>𤏸扇敶?Phase1.5-AI撖寡<E69296><E5AFA1><EFBFBD><EFBFBD><E69298><EFBFBD><E7A983>鞱扇敶?md`
**<2A><>捆憭抒熔**嚗?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撠𤩺𧒄嚗?
**瘚贝<E7989A><E8B49D>拚猐**嚗?
| <20>箸艶 | 颲枏<E9A2B2> | 憸<><E686B8>撌亙<E6928C> | 憸<><E686B8>颲枏枂 | <20><EFBFBD>?|
|------|------|---------|---------|------|
| 憿寧𤌍蝏蠘恣 | "<22>啣銁<E595A3><EFBFBD>憭𡁜<E686AD>鈭綽<E988AD>" | query_clinical_data | <20><><EFBFBD><EFBFBD>鈭箸㺭 | <20>?|
| <20><><EFBFBD><EFBFBD><EFBFBD>?| "P001敶訫<E695B6><EFBFBD><E988AD>嚗? | query_clinical_data | <20><><EFBFBD><E98984><EFBFBD><EFBFBD>𠶖<EFBFBD>?| <20>?|
| 韐冽綉<E586BD><EFBFBD>?| "<22>匧𪑛鈭𥡝捶<F0A5A19D>折䔮憸矋<E686B8>" | query_clinical_data | <20><EFBFBD><E6A185>𡑒” | <20>?|
| <20>𠉛弦<F0A0899B><EFBFBD> | "<22><EFBFBD><E4BAA4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E98A8B>" | search_knowledge_base | <20><EFBFBD><E5AF9E><EFBFBD>捆 | <20>?|
| CRF<52>亥砭 | "BMI<4D>𦒘<EFBFBD>憛恬<E6869B>" | search_knowledge_base | CRF霂湔<E99C82> | <20>?|
| <20>冽𥁒<E586BD>亥砭 | "銝𠰴𪂹餈𥕦<E9A488><EFBFBD><E68692>嚗? | search_knowledge_base | <20>冽𥁒<E586BD><F0A58192>捆 | <20>?|
| <20><EFBFBD> | "雿惩末" | <20>?| <20>见末<E8A781>𧼮<EFBFBD> | <20>?|
---
## <20><> <20><EFBFBD><E49C98><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>撉峕𤣰
### 4.1 <20><EFBFBD>摰峕㟲<E5B395>?
| <20><EFBFBD> | 撉峕𤣰<E5B395><F0A4A3B0><EFBFBD> | 隡睃<E99AA1>蝥?|
|------|---------|-------|
| **Dify<66><79><EFBFBD>** | <20><EFBFBD><E88880><EFBFBD><E8A098>冽𧋦<E586BD>蚤ify API | <20>𣞁 P0 |
| **<2A>誩㦛霂<E3A69B><E99C82>** | <20><><EFBFBD>?80% | <20>𣞁 P0 |
| **<2A>唳旿<E594B3>亥砭** | <20>舀䰻霂㎞EDCap摰墧𧒄<E5A2A7>唳旿 | <20>𣞁 P0 |
| **<2A><EFBFBD><EFBFBD>蝝?* | <20><EFBFBD><E89D9D>蝛嗆䲮獢<E4B2AE><E78DA2>獢?| <20>𣞁 P0 |
| **隡<><E99AA1>敺桐縑<E6A190>𧼮<EFBFBD>** | <20>𧼮<EFBFBD><F0A7BCAE>園𡢿<3蝘?| <20>𣞁 P0 |
| **<2A>冽𥁒<E586BD>芸𢆡敶埝﹝** | 瘥誩𪂹銝<F0AA82B9><E98A9D>芸𢆡<E88AB8><F0A286A1><EFBFBD> | <20><> P1 |
| **摰∟恣<E2889F><EFBFBD>** | <20><><EFBFBD>匧笆霂脲<E99C82><E884B2><EFBFBD> | <20><> P1 |
### 4.2 <20><EFBFBD><E689AF><EFBFBD><EFBFBD>
| <20><><EFBFBD> | <20><EFBFBD> | 霂湔<E99C82> |
|------|------|------|
| **<2A>𧼮<EFBFBD>撱嗉<E692B1>** | <3蝘?| <20><EFBFBD><E586BD>?<3F>?<3F><EFBFBD><E59785>𧼮<EFBFBD> |
| **Dify<66>亥砭撱嗉<E692B1>** | <500ms | <20>砍𧑐<E7A08D>函蔡嚗<E894A1><E59A97>霂亙<E99C82>敹?|
| **REDCap<61>亥砭撱嗉<E692B1>** | <1蝘?| 撌脫<E6928C>adapter嚗<72>歇撉諹<E69289> |
| **<2A>誩㦛霂<E3A69B><E99C82><EFBFBD><EFBFBD><EFBFBD>?* | >80% | <20><EFBFBD>瘚贝<E7989A><E8B49D>拚猐撉諹<E69289> |
| **<2A><EFBFBD><EFBFBD><E89D9D>蝖桃<E89D96>** | >70% | 靘肽<E99D98><E882BD><EFBFBD>﹝韐券<E99F90> |
### 4.3 隞<><E99A9E>韐券<E99F90>
- <20>?TypeScript蝐餃<E89D90>摰峕㟲
- <20>?<3F><EFBFBD>瘚贝<E7989A><EFBFBD><E996AC><EFBFBD>?70%
- <20>?<3F><><EFBFBD>瘚贝<E7989A><E8B49D><EFBFBD>
- <20>?<3F>躰秤憭<E7A7A4><E686AD><EFBFBD><E691B0>
- <20>?<3F><EFBFBD>霈啣<E99C88>摰峕㟲
- <20>?隞<><E99A9E>蝚血<E89D9A><EFBFBD><E996AB>
---
## <20><> 鈭𢛵<E988AD><F0A29BB5><EFBFBD>蝵脖<E89DB5>銝羓瑪
### 5.1 <20><EFBFBD><E887AC><EFBFBD><E3979B>滨蔭
```bash
# 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>蝘?
```bash
# 瘛餃<E7989B><E9A483>冽𥁒銵?npx prisma db push
npx prisma generate
```
### 5.3 <20>滚鍳<E6BB9A>滚𦛚
```bash
cd AIclinicalresearch/backend
npm run dev
```
---
## <20><> <20><EFBFBD><E58786><EFBFBD><EFBFBD><EFBFBD>箏𦛚銝擧㺿餈𥡝恣<F0A5A19D>?
### 6.1 敶枏<E695B6><E69E8F>𣂼<EFBFBD>嚗㇊hase 1.5嚗?
| <20>𣂼<EFBFBD> | 敶勗<E695B6> | 霈<E99C88><E288AA><EFBFBD><E5AFA1>園𡢿 |
|------|------|------------|
| **<2A>閖★<E99696>格𣈲<E6A0BC>?* | <20><EFBFBD><E88ABE>滚𦛚銝<F0A69B9A>銝芷★<E88AB7>?| Phase 2 |
| **<2A><EFBFBD>頧桀笆霂?* | 瘥𤩺活<F0A4A9BA><EFBFBD><E6A183><EFBFBD> | Phase 2 |
| **<2A><EFBFBD>銝𧢲<E98A9D>霈啣<E99C88>** | 銝滩扇敺𦯀<E695BA><F0A6AF80><EFBFBD>撖寡<E69296> | Phase 2 |
| **蝖祉<E89D96><E7A589><EFBFBD>䰻霂<E4B0BB><E99C82>ID** | 銝齿𣈲<E9BDBF><F0A388B2><EFBFBD>憿寧𤌍 | Phase 2 |
| **蝞<><E89D9E><EFBFBD><E99699><EFBFBD><E69B87>?* | 銝齿𣈲<E9BDBF><F0A388B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?| Phase 3 |
### 6.2 Phase 2 <20><EFBFBD><E99C88>
1. **憭𡁻★<F0A181BB>格𣈲<E6A0BC>?*
- 瘥譍葵憿寧𤌍<E5AFA7><EFBFBD><E7A589><EFBFBD>摨? - <20><EFBFBD><E586BD><EFBFBD><EFBFBD>蝞∠<E89D9E>
- 憿寧𤌍<E5AFA7><F0A48C8D><EFBFBD><EFBFBD>
2. **憭朞蔭撖寡<E69296>**
- 隡朞<E99AA1><E69C9E><EFBFBD><E59786><EFBFBD>? - 銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹<E68987><E695B9>Redis嚗? - 瞉<><E79E89>撘𤩺<E69298><F0A4A9BA>?
3. **瘛瑕<E7989B><E79195><EFBFBD>嚗㇌eAct嚗?*
- <20><EFBFBD>憭齿<E686AD><E9BDBF>亥砭
- 憭𡁜極<F0A1819C><EFBFBD><E7919E>? - <20>芯蜓<E88AAF><EFBFBD>敺芰㴓
---
## <20>?銝<><E98A9D><EFBFBD><EFBFBD><EFBFBD>
### 7.1 Phase 1.5<EFBFBD><EFBFBD>隞瑕<EFBFBD>?
**摰䂿緵<E482BF><EFBFBD>**嚗?- <20>?PI<50>臬銁隡<E98A81><E99AA1>敺桐縑銝剛䌊<E5899B>嗉祗閮<E7A597><E996AE>鞾䔮
- <20>?AI<41><EFBFBD><EFBFBD><E996AB><EFBFBD>曉僎<E69B89>亥砭<E4BAA5>唳旿/<2F><>
- <20>?<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>鞉𧋦
- <20><> <20>閙郊<E99699>誩㦛霂<E3A69B><E99C82>嚗𣬚<E59A97><F0A3AC9A><EFBFBD><E99696>?- <20>圲 憭滨鍂<E6BBA8><EFBFBD>RedcapAdapter
- <20><> <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%
- <20><> <20><>﹝摰峕㟲嚗𡁶鍂<F0A181B6><EFBFBD><E7919F>?撘<><E69298>𤏸扇敶?
---
**銝衤<E98A9D>甇?*嚗䥪hase 2 - 憭𡁻★<F0A181BB>格𣈲<E6A0BC><F0A388B2><EFBFBD>擃条漣撖寡<E69296><E5AFA1><EFBFBD>
---
## <20><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>
- <20>?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>
- <20>兛 **<2A><EFBFBD><E59786>?*嚗𡁜蘨<F0A1819C>充EDCap嚗䔶<E59A97><E494B6>求ify
- <20><> **<2A>㕑扇敹?*嚗𡁏𣈲<F0A1818F><F0A388B2><EFBFBD>頧桀笆霂嘅<E99C82>3頧殷<E9A0A7>
- <20>?**<2A><EFBFBD>擐?*嚗?甇<><EFBFBD>亥砭..."<22><EFBFBD><E8B8B9><EFBFBD><E586BD>西<EFBFBD>
- <20>㴓 **<2A><EFBFBD>憭毺鍂**嚗𡁏說頞?0%<25><>䰻霂<E99C82>瘙?
---
### 8.2 銝匧之<E58CA7><EFBFBD><E8A9A8><EFBFBD><EFBFBD>抅鈭𡒊鍂<F0A1928A>瑕遣霈殷<E99C88>
#### <20><EFBFBD>1嚗帋<E59A97>銝𧢲<E98A9D>霈啣<E99C88> <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><EFBFBD><EFBCB7>?const answer = await processQuery(userMessage);
await wechatService.sendMarkdownMessage(userId, answer);
```
**<2A><><EFBFBD>**嚗?- <20>?<3F><EFBFBD>蝡见朖<E8A781><EFBFBD><E8A781><EFBFBD>嚗?1蝘𡜐<E89D98>
- <20>?<3F>仿<EFBFBD>AI<41>典極雿?- <20>?銝滢<E98A9D><E6BBA2>西<EFBFBD>
---
#### <20><EFBFBD>3嚗𡁏<E59A97><EFBFBD>隡睃<E99AA1> <20>?
**<2A><EFBFBD>**嚗?- <20>蠘恣<E8A098>?憭拙<E686AD><E68B99>穃云<E7A983>?- Dify<66><79>𪂹<EFBFBD><EFBFBD><E4BAA6><EFBFBD><E8A098>𧼮<EFBFBD><F0A7BCAE><EFBFBD>
- <20><EFBFBD><E586BD><EFBFBD><EFBFBD><EFBFBD>嚗朞<E59A97>撖寡<E69296>
**閫<><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>:
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><><E99AA1>敺桐縑<E6A190><EFBFBD>撖寡<E69296><EFBFBD><E59A97>鈭页<E988AD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>㰘䌊摰帋<E691B0>UI
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> 銝𠹺<E98A9D><F0A0B9BA><EFBFBD><EFBFBD><EFBFBD>沐ode.js<6A><73><EFBFBD>
<20>?嚗<><EFBFBD><EFBFBD>擐?+ <20><><EFBFBD><E79899><EFBFBD><EFBFBD>
Phase 3嚗<33>𧊋<EFBFBD><EFBFBD>:
<EFBFBD><EFBFBD><EFBFBD><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>
<EFBFBD><EFBFBD><EFBFBD><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>
```bash
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>
| 璉<><E79289>仿★ | <20><><EFBFBD> | 撉諹<E69289><E8ABB9><EFBFBD> |
|-------|------|---------|
| <20>?<3F><EFBFBD>撖寡<E69296> | <20><EFBFBD>蝑?<3F><EFBFBD>憭𡁜<E686AD>鈭? | 隡<>凝瘚贝<E7989A> |
| <20>?<3F><><EFBFBD><EFBFBD>䰻霂?| <20><EFBFBD>蝑?P001<30><31><EFBFBD>" | 隡<>凝瘚贝<E7989A> |
| <20>?銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹?| "隞𡝗<E99A9E>銝滩<E98A9D><E6BBA9><EFBFBD><E6BB9A>?<3F><EFBFBD><E8B3AA>促001 | 隡<>凝瘚贝<E7989A> |
| <20>?甇<>銁颲枏<E9A2B2><E69E8F><EFBFBD> | <1蝘埝𤣰<E59F9D>?甇<><EFBFBD>亥砭..." | 隡<>凝瘚贝<E7989A> |
| <20>?<3F><><EFBFBD><E89D8F>憭?| <3蝘埝𤣰<E59F9D><EFBFBD><E595A3><EFBFBD>獢?| <20>𡒊垢<F0A1928A><EFBFBD> |
| <20>?摰∟恣<E2889F><EFBFBD> | 霈啣<E99C88>銝𠹺<E98A9D><F0A0B9BA><EFBFBD><EFBFBD>霈?| <20>唳旿摨𤘪<E691A8><F0A498AA>?|
---
### 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>
- <20><> <20>笔漲嚗?憭拐<E686AD>蝥?
**摰峕㟲<E5B395><E39FB2><EFBFBD>5憭抬<E686AD>**嚗?- <20><20><EFBFBD>嚗𡁜<E59A97><F0A1819C><EFBFBD>AI<41><EFBFBD><E68B87><EFBFBD>
- <20>𣑐 <20><>凒嚗? Dify<66><EFBFBD>摨?+ <20>冽𥁒敶埝﹝ + <20><><EFBFBD>亥砭
- <20><20>鞉𧋦嚗𡁻<E59A97><F0A181BB>滨蔭Dify嚗<79><EFBFBD><E6AD87>ocker嚗?- <20><> <20>笔漲嚗?憭拐<E686AD>蝥?
**撱箄悅**嚗?<3F>?**<2A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?*嚗?憭抬<E686AD>嚗屸<E59A97><EFBFBD><EFBFBD>?
<EFBFBD>?<3F><EFBFBD><E59C92><EFBFBD><E586BD><EFBFBD>
<EFBFBD>?<3F><EFBFBD>摰𡁏糓<F0A1818F><EFBFBD>摰峕㟲<E5B395>?
**摰鮋<E691B0><E9AE8B><EFBFBD>**嚗?<3F>?**<2A><><EFBFBD><EFBFBD><EFBFBD>歇摰峕<E691B0>**嚗?026-01-03嚗?
<EFBFBD>?**Dify<66><EFBFBD>摨枏歇<E69E8F><E6AD87><EFBFBD>**嚗?026-01-04嚗?
<EFBFBD>?**瘛瑕<E7989B><EFBFBD>歇摰䂿緵**嚗鑹EDCap摰墧𧒄<E5A2A7>唳旿 + Dify<66><79><EFBFBD><EFBFBD>摨?
---
## <20><> <20><EFBFBD><E68092>ify<66><EFBFBD>摨㯄<E691A8><E3AF84><EFBFBD>2026-01-04摰峕<E691B0>嚗?
### 8.7 <20><><EFBFBD><EFBFBD>峕艶
**摰峕<E691B0><E5B395>園𡢿**: 2026-01-04
**撘<><E69298>穃極雿𣈯<E99BBF>**: 4-6撠𤩺𧒄
**<2A><><EFBFBD><EFBFBD><EFBFBD>**: <20>沖EDCap摰墧𧒄<E5A2A7>唳旿<E594B3>亥砭<E4BAA5><EFBFBD>銝𠺪<E98A9D>憓𧼮<E68693><F0A7BCAE>𠉛弦<F0A0899B><EFBFBD><E5AF9E><EFBFBD><EFBFBD>亥砭<E4BAA5><EFBFBD>
**<2A><EFBFBD>隞瑕<E99A9E>?*嚗?- <20><> **<2A><><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>㴓 **<2A><EFBFBD>頝舐眏**: <20>寞旿<E5AF9E><EFBFBD><E586BD><EFBFBD><E6A185>芸𢆡<E88AB8>㗇𥋘<E39787>唳旿皞?
### 8.8 <20><><EFBFBD>舀䲮獢?
#### <20><EFBFBD><E5AF9E>㗇𥋘
| 蝏游漲 | <20><><EFBFBD><EFBFBD> |
|------|---------|
| **<2A><EFBFBD>摨𤘪沲<F0A498AA>?* | <20>閖★<E99696><EFBFBD><E6A180><EFBFBD>摨橒<E691A8>1銝杷IT憿寧𤌍 <20>?1銝枋ify Dataset嚗?|
| **<2A><>﹝銝𠹺<E98A9D>** | Dify Web<65>屸𢒰<E5B1B8>见𢆡銝𠹺<E98A9D>嚗㇈VP<56>嗆挾嚗?|
| **憿寧𤌍<E5AFA7><EFBFBD>** | <20><EFBFBD>蝏穃<E89D8F>暺䁅恕憿寧𤌍嚗<F0A48C8D><E59A97><EFBFBD>典銁`iit_schema.projects.dify_dataset_id`嚗?|
#### <20><EFBFBD>摰䂿緵
**1. <20><EFBFBD><E68B99>誩㦛霂<E3A69B><E99C82>**
<EFBFBD>灼ChatService.detectIntent()`銝剜鰵憓裇query_protocol`<60>誩㦛嚗?
```typescript
// 霂<><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>**
```typescript
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><E89D9D><EFBCB9>? // 靽桀<E99DBD>bug嚗帋蝙<E5B88B>冽迤蝖桃<E89D96>摮埈挾頝臬<E9A09D> record.segment.document.name <20>?record.segment.content
// ...
}
```
**3. <20>湔鰵撖寡<E69296><EFBFBD><E7989A>**
```typescript
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>埝䰻銝𦒘耨憭?
#### <20><EFBFBD>1: AI銝齿䰻霂¥ify嚗諹䌊撌梁<E6928C><E6A281><EFBFBD>獢?
**<2A>啗情**: <20><EFBFBD><E586BD>?蝥喳<E89DA5><E596B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E98A8B>"嚗淾I蝻㚚<E89DBB><EFBFBD>蝑娍<E89D91>嚗㷉ify<66><EFBFBD><E689B9><EFBFBD><E594B3>亥砭霈啣<E99C88>
**<2A><EFBFBD>1**: <20>誩㦛霂<E3A69B><E99C82><EFBFBD>喲睸霂滢<E99C82><E6BBA2>?- **蝻箏<E89DBB>**: "<22>仿<EFBFBD>?<3F>?霂𦠜鱏<F0A6A09C><E9B18F><EFBFBD>"<22>?<3F><EFBFBD><E69B84><EFBFBD><EFBFBD>"
- **閫<><E996AB>**: <20><EFBFBD><E68B99>喲睸霂滚<E99C82>銵?
**<2A><EFBFBD>2**: Dify API餈𥪜<E9A488>摮埈挾頝臬<E9A09D><E887AC>躰秤
- **<2A>躰秤**: `record.document_name`<60><>record.content` <20>?餈𥪜<E9A488>`undefined`
- **甇<>**: `record.segment.document.name`<EFBFBD><EFBFBD>record.segment.content`
- **閫<><E996AB>**: 靽格迤摮埈挾霈輸䔮頝臬<E9A09D>
**靚<><E99D9A><EFBFBD><E9A488>**:
1. <20>𥕦遣`debug-dify-injection.ts`餈質葵<EFBFBD>唳旿瘜典<EFBFBD><EFBFBD><EFBFBD>
2. <20>𥕦遣`inspect-dify-response.ts`<EFBFBD><EFBFBD>Dify API摰鮋<E691B0>餈𥪜<E9A488>蝏𤘪<E89D8F>
3. <20>𤑳緵撟嗡耨憭滚<E686AD>畾菔楝敺<E6A59D><E695BA>霂?
### 8.10 瘚贝<E7989A>撉諹<E69289>
| 瘚贝<E7989A><E8B49D>箸艶 | <20><EFBFBD> | <20>唳旿皞?| 蝏𤘪<E89D8F> |
|---------|------|--------|------|
| **<EFBFBD><EFBFBD><EFBFBD>亥砭** | "餈嗘葵<E59798>𠉛弦<F0A0899B><E5BCA6><EFBFBD><EFBFBD><EFBFBD><E69697><EFBFBD>糓隞<E7B393><EFBFBD><E98A8B>" | Dify | <20>?<3F>𣂼<EFBFBD> |
| **CRF<52>亥砭** | "CRF銵冽聢銝剜<E98A9D><E5899C><EFBFBD><EFBFBD><E996AB><EFBFBD><EFBFBD><EFBFBD>嚗? | Dify | <20>?<3F>𣂼<EFBFBD> |
| **<EFBFBD><EFBFBD><EFBFBD><EFBFBD>䰻霂?* | "ID 7<><37><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>? | REDCap | <20>?<3F>𣂼<EFBFBD> |
| **蝏蠘恣<E8A098>亥砭** | "<22><EFBFBD><E6A180><EFBFBD><EFBFBD><E988AD>撠睲犖嚗? | REDCap | <20>?<3F>𣂼<EFBFBD> |
| **瘛瑕<E7989B><E79195>亥砭** | "餈嗘葵<E59798>𠉛弦<F0A0899B><E5BCA6>蜓閬<E89C93><E996AC>蝛嗥𤌍<E597A5><F0A48C8D>糓隞<E7B393><EFBFBD><E98A8B>" | Dify | <20>?<3F>𣂼<EFBFBD> |
### 8.11 <20><><EFBFBD><EFBFBD><EFBFBD>
**<EFBFBD><EFBFBD><EFBFBD>舀沲<EFBFBD>?*:
```
<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>**:
1. <20>?**瘛瑕<E7989B><EFBFBD>蝝?*: <20>峕𧒄<E5B395><EFBFBD>蝏𤘪<E89D8F><F0A498AA>𡝗㺭<F0A19D97><EFBFBD><E6A180><EFBFBD><E482BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
2. <20>?**<2A><EFBFBD>頝舐眏**: <20>寞旿<E5AF9E>誩㦛<E8AAA9>芸𢆡<E88AB8>㗇𥋘<E39787>唳旿皞?3. <20>?**<2A>脫迫撟餉<E6929F>**: <20><><EFBFBD><EFBFBD>蝑𥪜抅鈭𡒊<E988AD>摰墧㺭<E5A2A7>?<3F><>
4. <20>?**<2A><EFBFBD><E4BAA4><EFBFBD>釣**: 皜<><EFBFBD><E88B8A><EFBFBD>唳旿<E594B3>亥䌊REDCap<61>靝ify
**霂衣<E99C82>霈啣<E99C88>**: <20><><EFBFBD> [Dify<EFBFBD><EFBFBD>摨㯄<EFBFBD><EFBFBD>𣂼<EFBFBD><EFBFBD>𤏸扇敶騟(../06-撘<><E69298>𤏸扇敶?2026-01-04-Dify<66><EFBFBD>摨㯄<E691A8><E3AF84>𣂼<EFBFBD><F0A382BC>𤏸扇敶?md)
---
## <20>?銋腈<E98A8B><E88588><EFBFBD><EFBFBD>
### <20><EFBFBD><E8A9A8>𣂼停嚗<E5819C><E59A97><EFBFBD><E89D9E>?+ Dify<66><79><EFBFBD>嚗?
1. <20>?**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>
3. <20>?**甇<>銁颲枏<E9A2B2><E69E8F><EFBFBD>**嚗𡁻<E59A97><F0A181BB>滨鍂<E6BBA8><EFBFBD><E7919E>?4. <20>?**隞<><E99A9E><EFBFBD><E996AB>**嚗?隞?<3F>質䌊<E8B3AA><EFBFBD><E588BB><EFBFBD><E680A5>?5. <20>?**瘛瑕<E7989B><EFBFBD>蝝?*嚗𡁜<E59A97><F0A1819C>嗆𣈲<E59786><F0A388B2>EDCap摰墧𧒄<E5A2A7>唳旿 + Dify<66><79><EFBFBD><EFBFBD>摨?6. <20>?**<2A>脫迫撟餉<E6929F>**嚗𡁏<E59A97><F0A1818F><EFBFBD>蝑𥪜抅鈭𡒊<E988AD>摰墧㺭<E5A2A7><EFBFBD>蝏苷<E89D8F>蝻㚚<E89DBB>?
### <20><><EFBFBD>臭漁<E887AD>?
- <20><> **SessionMemory**嚗𡁜<E59A97>摮睃<E691AE><E79D83><EFBFBD><E58981>𣳇<EFBFBD>Redis
- <20>㴓 **<2A>閙郊頝舐眏**嚗帋<E59A97><E5B88B><EFBFBD><E585B8><EFBFBD>eAct敺芰㴓
- <20>圲 **憭滨鍂<E6BBA8><EFBFBD>**嚗鑹edcapAdapter + WechatService
- <20>?**<2A><EFBFBD>靽肽<E99DBD>**嚗?3蝘垍垢<E59E8D>啁垢撱嗉<E692B1>
- <20><> **摰∟恣摰峕㟲**嚗朞扇敶閙<E695B6><E99699>匧笆霂?
### <20><EFBFBD>隞瑕<E99A9E>?
**Before嚗㇄ay 3嚗?*嚗?- <20>?PI<50>臭誑<E887AD>交𤣰隡<F0A4A3B0><E99AA1>敺桐縑<E6A190>𡁶䰻
- <20>?PI<50><EFBFBD>銝餃𢆡<E9A483>亥砭<E4BAA5>唳旿
- <20>?<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>
- <20>?<3F>𧼮<EFBFBD>敹恍<E695B9><E6818D><EFBFBD><6蝘𡜐<E89D98>嚗峕<E59A97><E5B395><EFBFBD>
- <20>?AI<41><EFBFBD><E7AE94><EFBFBD><E7AC94>唳旿/<2F><><EFBFBD><EFBFBD>嚗䔶<E59A97>蝻㚚<E89DBB>?
---
## <20><> Phase 1.5 撘<><E69298><EFBFBD><E7A983><EFBFBD><EFBFBD> (2026-01-03 & 2026-01-04)
### **摰鮋<E691B0>摰峕<E691B0><E5B395><EFBFBD><EFBFBD>**
- <20>?**Day 1摰峕<E691B0>** (2026-01-03): SessionMemory + ChatService + REDCap<61><70><EFBFBD>
- <20>?**Day 2摰峕<E691B0>** (2026-01-04): Dify<66><EFBFBD>摨㯄<E691A8><E3AF84>?+ 瘛瑕<E7989B><EFBFBD>蝝?- <20>?**瘚贝<E7989A><E8B49D><EFBFBD>**: 隡<><E99AA1>敺桐縑撖寡<E69296> + <20><EFBFBD><E7AC94>唳旿<E594B3>亥砭 + <20><><EFBFBD>亥砭
- <20>?**<2A><EFBFBD><EFBFBD>聦**: 閫<><E996AB>LLM撟餉<E6929F><E9A489><EFBFBD> + 瘛瑕<E7989B><EFBFBD><EFBCB8>?
### **<2A>喲睸<E596B2><EFBFBD>**
1. <20>?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><E89D9D><EFBD87>峕𧒄<E5B395><EFBFBD>蝏𤘪<E89D8F><F0A498AA>𡝗㺭<F0A19D97><EFBFBD><E6A180><EFBFBD><E482BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
4. <20>?隞擧㺭<E693A7><EFBFBD>霂餃<E99C82>憿寧𤌍<E5AFA7>滨蔭嚗ōest0102嚗?5. <20>?<3F>誩㦛霂<E3A69B><E99C82> + <20><EFBFBD>頝舐眏 + <20>唳旿<E594B3>亥砭 + LLM<4C><4D><EFBFBD>
6. <20>?銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹<E68987><E695B9><EFBFBD><EFBFBD>餈?頧桀笆霂嘅<E99C82>
7. <20>?<3F>單𧒄<E596AE><EFBFBD>嚗?甇<><EFBFBD>亥砭"嚗?
### **瘚贝<E7989A>撉諹<E69289>**
- **憿寧𤌍**: test0102
- REDCap PID: 16, 11<31>∟扇敶? - Dify Dataset ID: `b49595b2-bf71-4e47-9988-4aa2816d3c6f`
- <20><>﹝: <20>𠉛弦<F0A0899B><EFBFBD><E5AF9E><EFBFBD>RF銵冽聢嚗?銝芣<E98A9D>隞塚<E99A9E>撌脣<E6928C><E884A3><EFBFBD><EFBFBD>
- **<2A>箸艶1**: <20>亥砭ID 7<><37><EFBFBD><EFBFBD><EFBFBD><EFBFBD>REDCap嚗争<E59A97> <20>?摰<><E691B0><EFBFBD><EFBFBD><E5AFA5><EFBFBD><E7AC94>唳旿
- **<2A>箸艶2**: <20>亥砭<E4BAA5>𠉛弦<F0A0899B>㘾膄<E398BE><E88684><EFBFBD>嚗㇄ify嚗争<E59A97> <20>?<3F><EFBFBD><E7AE94><EFBFBD><EFBFBD><EFB99D><EFBFBD><EFBFBD>
- **<2A>箸艶3**: <20>亥砭CRF閫<46><E996AB><EFBFBD><EFBFBD><EFBFBD>嚗㇄ify嚗争<E59A97> <20>?<3F><EFBFBD><E7AE94><EFBFBD><EFBFBD><EFB99D><EFBFBD><EFBFBD>
- **<2A>箸艶4**: 蝏蠘恣<E8A098><EFBFBD>鈭箸㺭嚗㇌EDCap嚗争<E59A97> <20>?<3F><>蝏蠘恣11鈭?- **蝏𤘪<E89D8F>**: <20>?<3F><><EFBFBD><EFBFBD>霂閖<E99C82><EFBFBD>嚗峕<E59A97>蝻㚚<E89DBB>?
### **霂衣<E99C82>霈啣<E99C88>**
- [Phase 1.5撘<EFBFBD><EFBFBD><EFBFBD><EFBFBD>鞱扇敶?(REDCap<61><70><EFBFBD>)](../06-撘<><E69298>𤏸扇敶?Phase1.5-AI撖寡<E69296><E5AFA1><EFBFBD><EFBFBD>REDCap摰峕<E691B0>霈啣<E99C88>.md)
- [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>?
**<EFBFBD><EFBFBD><EFBFBD>擧凒<EFBFBD>?*嚗?026-01-03
**<EFBFBD><EFBFBD><EFBFBD><EFBFBD>?*嚗尠<E59A97> Phase 1.5撌脣<EFBFBD><EFBFBD>?