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%)
2749 lines
83 KiB
Markdown
2749 lines
83 KiB
Markdown
# 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>蝝g<E89D9D>2026-01-04摰峕<E691B0>嚗? - <20>唾<EFBFBD>憿寧𤌍銝𥟠ify<66>亥<EFBFBD>摨橒<E691A8>1撠𤩺𧒄嚗? - <20><><EFBFBD>Dify璉<79>蝝W<E89D9D>ChatService嚗?撠𤩺𧒄嚗? - 靽桀<E99DBD><E6A180>誩㦛霂<E3A69B><E99C82>銝擧㺭<E693A7>格釣<E6A0BC>半ug嚗?撠𤩺𧒄嚗? - 蝡臬<E89DA1>蝡舀<E89DA1>霂蓥<E99C82><E893A5><EFBFBD>﹝霈啣<E99C88>嚗?撠𤩺𧒄嚗?<3F>?<3F><><EFBFBD>摰䂿緵: <20>冽𥁒<E586BD><F0A58192><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ool Calling
|
||
```
|
||
|
||
### <20><><EFBFBD><EFBFBD><EFBFBD>沲<EFBFBD><E6B2B2><EFBFBD>憭滨鍂<E6BBA8>𡁶鍂<F0A181B6>賢<EFBFBD>撅<EFBFBD><E69285>
|
||
|
||
```
|
||
PI<EFBFBD>鞾䔮 <20>?Node.js <20>?LLMFactory(deepseek-v3) <20>?<3F><><EFBFBD><EFBFBD>䂿<EFBFBD> <20>?隡<><E99AA1>敺桐縑<E6A190>券<EFBFBD>? <20>? <20>? SessionMemory RedcapAdapter嚗<72>虾<EFBFBD>㚁<EFBFBD>
|
||
```
|
||
|
||
**<2A>喲睸<E596B2>喟<EFBFBD>**嚗?- <20><> **憭滨鍂LLMFactory**嚗<>歇<EFBFBD>㚁<EFBFBD><E39A81>嗅<EFBFBD><E59785>𡢅<EFBFBD><F0A1A285>刻<EFBFBD>`deepseek-v3`嚗?- <20>?**<2A>芣䰻REDCap<61>唳旿**嚗<>歇<EFBFBD>兴edcapAdapter嚗<72><E59A97><EFBFBD>典朖<E585B8>荔<EFBFBD>
|
||
- <20>?**銝齿𦻖Dify**嚗<><E59A97>撠睲<E692A0>韏吔<E99F8F><E59094>惩翰撘<E7BFB0><E69298>𡢅<EFBFBD>
|
||
- <20>?**銝𠹺<E98A9D><F0A0B9BA><EFBFBD>扇敹?*嚗𠃊ode.js<6A><73><EFBFBD>嚗<EFBFBD><E59A97><EFBFBD><EFBFBD>餈?頧殷<E9A0A7>
|
||
- <20>?**甇<>銁颲枏<E9A2B2><E69E8F>漤<EFBFBD>**嚗<><E59A97><EFBFBD>喳<EFBFBD>"甇<>銁<EFBFBD>亥砭..."嚗?- <20>?**<2A>閙郊頝舐眏**嚗<><E59A97><EFBFBD>沖eAct敺芰㴓嚗?
|
||
**憸<>摯撌乩<E6928C><E4B9A9>誩之撟<E4B98B><E6929F>雿?*嚗?-3憭?<3F>?**1憭?*嚗<><E59A97>銝摔LM靚<4D>鍂撅<E98D82>歇摰<E6AD87><E691B0>嚗争黾
|
||
|
||
---
|
||
|
||
## <20>㴓 銝<><E98A9D><EFBFBD>瓲敹<E793B2>𤌍<EFBFBD><F0A48C8D><EFBFBD>隞瑕<E99A9E>?
|
||
### 1.1 銝箔<E98A9D>銋<EFBFBD><E98A8B>閬<EFBFBD>I撖寡<E69296><E5AFA1>賢<EFBFBD>嚗?
|
||
**敶枏<E695B6><E69E8F>嗆<EFBFBD>?*嚗㇄ay 3撌脣<E6928C><E884A3>琜<EFBFBD>嚗?```
|
||
<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>蝝?*嚗𡁏䰻霂Y<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>賣䰻霂X<E99C82>獢?- <20>䂿<EFBFBD>閬<EFBFBD><E996AC>瘣<EFBFBD><E798A3>銝䫤;
|
||
}
|
||
|
||
/**
|
||
* 摰帋<E691B0><E5B88B>唳旿<E594B3>亥砭撌亙<E6928C>
|
||
*/
|
||
private getDataQueryTool(): any {
|
||
return {
|
||
type: "function",
|
||
function: {
|
||
name: "query_clinical_data",
|
||
description: `<60>亥砭REDCap銝游<E98A9D><E6B8B8>唳旿<E594B3>?<3F><>鍂<EFBFBD>箸艶嚗?- <20>桅★<E6A185>桃<EFBFBD>霈∴<E99C88><E288B4>啣銁<E595A3>亦<EFBFBD>憭𡁜<E686AD>鈭綽<E988AD><E7B6BD>唳旿韐券<E99F90>憒<EFBFBD><E68692>嚗?- <20>格<EFBFBD><E6A0BC><EFBFBD>祕<EFBFBD><E7A595><EFBFBD>P001<30><31><EFBFBD><EFBFBD><EFBFBD>摰䔶<E691B0><E494B6>梹<EFBFBD><E6A2B9>劐<EFBFBD><E58A90>臬<EFBFBD>摨𥪜<E691A8>嚗?- <20>株捶<E6A0AA>抒𠶖<E68A92><F0A0B696><EFBFBD><EFBFBD>匧𪑛鈭𥡝捶<F0A5A19D>折䔮憸矋<E686B8>`,
|
||
parameters: {
|
||
type: "object",
|
||
properties: {
|
||
intent: {
|
||
type: "string",
|
||
enum: ["project_stats", "patient_detail", "qc_status"],
|
||
description: `<60>亥砭<E4BAA5>誩㦛嚗?- project_stats: 憿寧𤌍蝏蠘恣
|
||
- patient_detail: <20><><EFBFBD><EFBFBD>祕<EFBFBD>?- qc_status: 韐冽綉<E586BD>嗆<EFBFBD><E59786>
|
||
},
|
||
patient_id: {
|
||
type: "string",
|
||
description: "<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>瘀<EFBFBD>憒<EFBFBD>001嚗㚁<E59A97>敶𧗽ntent=patient_detail<69>嗅<EFBFBD>憛?
|
||
}
|
||
},
|
||
required: ["intent"]
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 璉<><E79289>交<EFBFBD><E4BAA4>臭葉<E887AD>臬炏<E887AC>劐誨霂? */
|
||
private hasPronouns(message: string): boolean {
|
||
const pronouns = ['隞?, '憟?, '餈嗘葵<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>急䰻霂X<E99C82><EFBCB8>?- <20>?<3F>舀<EFBFBD>銝𠹺<E98A9D><F0A0B9BA><EFBFBD><EFBFBD>閫<EFBFBD><E996AB>隞<EFBFBD><E99A9E>閫<EFBFBD><E996AB>嚗?- <20>?<3F>躰秤憭<E7A7A4><E686AD>摰<EFBFBD><E691B0>
|
||
|
||
---
|
||
|
||
#### 隞餃𦛚1.3嚗𡁶<EFBFBD><EFBFBD>巁oolExecutor嚗?.5撠𤩺𧒄嚗?
|
||
**<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>䰻霂Y掩<EFBCB9>?
|
||
};
|
||
}
|
||
} catch (error: any) {
|
||
logger.error('[SimpleToolExecutor] Execution failed', {
|
||
error: error.message
|
||
});
|
||
|
||
return {
|
||
success: false,
|
||
data: null,
|
||
error: error.message
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* <20>瑕<EFBFBD>憿寧𤌍蝏蠘恣
|
||
*/
|
||
private async getProjectStats(
|
||
redcap: RedcapAdapter,
|
||
projectName: string
|
||
): Promise<ToolExecutionResult> {
|
||
const records = await redcap.exportRecords();
|
||
|
||
return {
|
||
success: true,
|
||
data: {
|
||
type: 'project_stats',
|
||
projectName,
|
||
stats: {
|
||
totalRecords: records.length,
|
||
enrolled: records.length,
|
||
completed: records.filter((r: any) => r.complete === '2').length,
|
||
dataQuality: this.calculateDataQuality(records)
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* <20>瑕<EFBFBD><E79195><EFBFBD><EFBFBD><EFBFBD>祕<EFBFBD>? */
|
||
private async getPatientDetail(
|
||
redcap: RedcapAdapter,
|
||
patientId: string
|
||
): Promise<ToolExecutionResult> {
|
||
const records = await redcap.exportRecords([patientId]);
|
||
|
||
if (records.length === 0) {
|
||
return {
|
||
success: false,
|
||
data: null,
|
||
error: `<60>芣𪄳<E88AA3>唳<EFBFBD><E594B3>?${patientId}`
|
||
};
|
||
}
|
||
|
||
const record = records[0];
|
||
|
||
return {
|
||
success: true,
|
||
data: {
|
||
type: 'patient_detail',
|
||
patientId,
|
||
details: {
|
||
age: record.age,
|
||
gender: record.gender,
|
||
bmi: record.bmi,
|
||
complete: record.complete === '2' ? '撌脣<EFBFBD><EFBFBD>? : '餈𥡝<E9A488>銝?,
|
||
lastUpdate: new Date().toISOString()
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* <20>瑕<EFBFBD>韐冽綉<E586BD>嗆<EFBFBD>? */
|
||
private async getQCStatus(projectId: string): Promise<ToolExecutionResult> {
|
||
const logs = await prisma.iitAuditLog.findMany({
|
||
where: {
|
||
projectId,
|
||
actionType: 'quality_issue'
|
||
},
|
||
orderBy: { createdAt: 'desc' },
|
||
take: 10
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
data: {
|
||
type: 'qc_status',
|
||
issueCount: logs.length,
|
||
recentIssues: logs.map(log => ({
|
||
recordId: log.entityId,
|
||
issue: log.details,
|
||
createdAt: log.createdAt
|
||
}))
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 霈∠<E99C88><E288A0>唳旿韐券<E99F90>嚗<EFBFBD><E59A97><EFBFBD>閧<EFBFBD>瘜𤏪<E7989C>
|
||
*/
|
||
private calculateDataQuality(records: any[]): string {
|
||
if (records.length === 0) return '0%';
|
||
|
||
const completedCount = records.filter((r: any) => r.complete === '2').length;
|
||
const quality = (completedCount / records.length) * 100;
|
||
|
||
return `${quality.toFixed(1)}%`;
|
||
}
|
||
}
|
||
|
||
export interface ToolExecutionResult {
|
||
success: boolean;
|
||
data: any;
|
||
error?: string;
|
||
}
|
||
```
|
||
|
||
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>舀䰻霂a★<EFBD81>桃<EFBFBD>霈?- <20>?<3F>舀䰻霂X<E99C82><EFBCB8><EFBFBD>祕<EFBFBD>?- <20>?<3F>舀䰻霂Z捶<EFBCBA>抒𠶖<E68A92>?
|
||
---
|
||
|
||
#### 隞餃𦛚1.4嚗𡁶<EFBFBD><EFBFBD>䨝nswerGenerator嚗?撠𤩺𧒄嚗?
|
||
**<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>V葵<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>蝝X<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>?璉<>蝝X<E89D9D>霂訫<E99C82>蝖桃<E89D96>>80%
|
||
|
||
**鈭批枂**嚗?- Dify<66>亥<EFBFBD>摨𨧻D
|
||
- API靚<49>鍂蝷箔<E89DB7>隞<EFBFBD><E99A9E>
|
||
|
||
---
|
||
|
||
#### 隞餃𦛚1.3嚗𡁜<EFBFBD><EFBFBD>蚤ify API<50><49><EFBFBD><EFBFBD>剁<EFBFBD>3撠𤩺𧒄嚗?
|
||
**<2A><>辣雿滨蔭**嚗䫤backend/src/modules/iit-manager/adapters/DifyAdapter.ts`
|
||
|
||
**隞<><E99A9E>摰䂿緵**嚗?
|
||
```typescript
|
||
import axios from 'axios';
|
||
import { logger } from '../../../common/logging/index.js';
|
||
|
||
/**
|
||
* Dify API<50><49><EFBFBD><EFBFBD>? * <20>其<EFBFBD>銝擧𧋦<E693A7>蚤ify Docker摰硺<E691B0>鈭支<E988AD>
|
||
*/
|
||
export class DifyAdapter {
|
||
private baseUrl: string;
|
||
private apiKey: string;
|
||
private knowledgeBaseId: string;
|
||
|
||
constructor(projectId: string) {
|
||
// 隞𡒊㴓憓<E3B493><E68693><EFBFBD>𤩺<EFBFBD><F0A4A9BA>唳旿摨栞粉<E6A09E>㚚<EFBFBD>蝵? this.baseUrl = process.env.DIFY_API_URL || 'http://localhost/v1';
|
||
this.apiKey = process.env.DIFY_API_KEY || '';
|
||
this.knowledgeBaseId = this.getKnowledgeBaseId(projectId);
|
||
}
|
||
|
||
/**
|
||
* <20>𦦵揣<F0A6A6B5>亥<EFBFBD>摨? * @param query <20>亥砭<E4BAA5>桅<EFBFBD>
|
||
* @param options <20>𦦵揣<F0A6A6B5>厰★
|
||
*/
|
||
async searchKnowledge(
|
||
query: string,
|
||
options?: {
|
||
doc_type?: 'protocol' | 'crf' | 'report';
|
||
top_k?: number;
|
||
}
|
||
): Promise<DifySearchResult> {
|
||
try {
|
||
logger.info('[DifyAdapter] Searching knowledge base', {
|
||
query,
|
||
options,
|
||
knowledgeBaseId: this.knowledgeBaseId
|
||
});
|
||
|
||
const response = await axios.post(
|
||
`${this.baseUrl}/datasets/${this.knowledgeBaseId}/retrieve`,
|
||
{
|
||
query: query,
|
||
retrieval_model: {
|
||
search_method: 'semantic_search',
|
||
top_k: options?.top_k || 3,
|
||
score_threshold: 0.5
|
||
},
|
||
// 憒<><E68692><EFBFBD><EFBFBD><EFBFBD>鈭<EFBFBD>oc_type嚗屸<E59A97>朞<EFBFBD>metadata餈<61>誘
|
||
...(options?.doc_type && {
|
||
retrieval_model: {
|
||
filter: {
|
||
doc_type: options.doc_type
|
||
}
|
||
}
|
||
})
|
||
},
|
||
{
|
||
headers: {
|
||
'Authorization': `Bearer ${this.apiKey}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
timeout: 10000 // 10蝘坿<E89D98><E59DBF>? }
|
||
);
|
||
|
||
logger.info('[DifyAdapter] Search completed', {
|
||
recordCount: response.data.records?.length || 0
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
records: response.data.records || [],
|
||
query: query
|
||
};
|
||
} catch (error: any) {
|
||
logger.error('[DifyAdapter] Search failed', {
|
||
error: error.message,
|
||
query
|
||
});
|
||
|
||
return {
|
||
success: false,
|
||
records: [],
|
||
query,
|
||
error: error.message
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 銝𠹺<E98A9D><F0A0B9BA><EFBFBD>﹝<EFBFBD>啁䰻霂<E4B0BB><E99C82>
|
||
* @param content <20><>﹝<EFBFBD><EFB99D>捆
|
||
* @param metadata <20><>㺭<EFBFBD>? */
|
||
async uploadDocument(
|
||
content: string,
|
||
metadata: {
|
||
name: string;
|
||
doc_type: 'protocol' | 'crf' | 'report';
|
||
date?: string;
|
||
}
|
||
): Promise<{ success: boolean; documentId?: string }> {
|
||
try {
|
||
logger.info('[DifyAdapter] Uploading document', {
|
||
name: metadata.name,
|
||
type: metadata.doc_type
|
||
});
|
||
|
||
const response = await axios.post(
|
||
`${this.baseUrl}/datasets/${this.knowledgeBaseId}/document/create_by_text`,
|
||
{
|
||
name: metadata.name,
|
||
text: content,
|
||
indexing_technique: 'high_quality',
|
||
process_rule: {
|
||
mode: 'automatic',
|
||
rules: {
|
||
pre_processing_rules: [
|
||
{ id: 'remove_extra_spaces', enabled: true },
|
||
{ id: 'remove_urls_emails', enabled: false }
|
||
],
|
||
segmentation: {
|
||
separator: '\n',
|
||
max_tokens: 500
|
||
}
|
||
}
|
||
},
|
||
doc_form: 'text_model',
|
||
doc_language: 'Chinese',
|
||
// 靽嘥<E99DBD><E598A5><EFBFBD>㺭<EFBFBD>? metadata: {
|
||
doc_type: metadata.doc_type,
|
||
date: metadata.date || new Date().toISOString(),
|
||
upload_time: new Date().toISOString()
|
||
}
|
||
},
|
||
{
|
||
headers: {
|
||
'Authorization': `Bearer ${this.apiKey}`,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
}
|
||
);
|
||
|
||
logger.info('[DifyAdapter] Document uploaded', {
|
||
documentId: response.data.document.id
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
documentId: response.data.document.id
|
||
};
|
||
} catch (error: any) {
|
||
logger.error('[DifyAdapter] Upload failed', {
|
||
error: error.message,
|
||
name: metadata.name
|
||
});
|
||
|
||
return {
|
||
success: false
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* <20>瑕<EFBFBD>憿寧𤌍撖孵<E69296><E5ADB5><EFBFBD>䰻霂<E4B0BB><E99C82>ID
|
||
* @param projectId 憿寧𤌍ID
|
||
*/
|
||
private getKnowledgeBaseId(projectId: string): string {
|
||
// TODO: 隞擧㺭<E693A7>桀<EFBFBD>霂餃<E99C82>憿寧𤌍<E5AFA7>滨蔭
|
||
// 銝湔𧒄<E6B994>寞<EFBFBD>嚗帋<E59A97><E5B88B>臬<EFBFBD><E887AC>㗛<EFBFBD>霂餃<E99C82>
|
||
return process.env.DIFY_KNOWLEDGE_BASE_ID || '';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Dify<66>𦦵揣蝏𤘪<E89D8F>
|
||
*/
|
||
export interface DifySearchResult {
|
||
success: boolean;
|
||
records: Array<{
|
||
content: string;
|
||
score: number;
|
||
metadata?: {
|
||
doc_type?: string;
|
||
date?: string;
|
||
};
|
||
}>;
|
||
query: string;
|
||
error?: string;
|
||
}
|
||
```
|
||
|
||
**<EFBFBD>臬<EFBFBD><EFBFBD>㗛<EFBFBD><EFBFBD>滨蔭**嚗Ǒ.env`嚗㚁<E59A97>
|
||
```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嚗𡁏䰻霂W<E99C82><EFBCB7>嗆㺭<E59786>? {
|
||
type: "function",
|
||
function: {
|
||
name: "query_clinical_data",
|
||
description: `<EFBFBD>鞉䰻REDCap摰墧𧒄<EFBFBD>唳旿<EFBFBD>𤑳鍂鈭擧䰻霂V葩摨羓<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>蝝Y䰻霂<E4B0BB><E99C82>
|
||
{
|
||
type: "function",
|
||
function: {
|
||
name: "search_knowledge_base",
|
||
description: `<EFBFBD>鞉䰻<EFBFBD>𠉛弦<EFBFBD><EFBFBD>﹝<EFBFBD>𤑳鍂鈭擧<EFBFBD>蝝Y<EFBFBD>蝛嗆䲮獢<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>隞嗚<EFBFBD><EFBFBD><EFBFBD><EFBFBD>脰扇敶閧<EFBFBD><EFBFBD>蹱<EFBFBD><EFBFBD><EFBFBD><EFBFBD>踺<EFBFBD>?<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>嚗𡁏䰻霂V葩摨𦠜㺭<F0A6A09C>? */
|
||
private async executeQueryClinicalData(
|
||
args: {
|
||
intent: 'project_stats' | 'patient_detail' | 'qc_status';
|
||
patient_id?: string;
|
||
date_range?: string;
|
||
},
|
||
context: { projectId: string; userId: string }
|
||
): Promise<ToolExecutionResult> {
|
||
// 1. <20>瑕<EFBFBD>憿寧𤌍<E5AFA7>滨蔭
|
||
const project = await prisma.iitProject.findUnique({
|
||
where: { id: context.projectId },
|
||
select: {
|
||
redcapApiUrl: true,
|
||
redcapApiToken: true,
|
||
redcapProjectId: true
|
||
}
|
||
});
|
||
|
||
if (!project) {
|
||
return {
|
||
success: false,
|
||
data: null,
|
||
error: '憿寧𤌍銝滚<E98A9D><E6BB9A>?
|
||
};
|
||
}
|
||
|
||
// 2. <20>嘥<EFBFBD><E598A5>餸edcapAdapter
|
||
const redcap = new RedcapAdapter(
|
||
project.redcapApiUrl,
|
||
project.redcapApiToken
|
||
);
|
||
|
||
// 3. <20>寞旿intent<6E>扯<EFBFBD>銝滚<E98A9D><E6BB9A>亥砭
|
||
switch (args.intent) {
|
||
case 'project_stats': {
|
||
// <20>亥砭憿寧𤌍蝏蠘恣
|
||
const records = await redcap.exportRecords();
|
||
|
||
return {
|
||
success: true,
|
||
data: {
|
||
type: 'project_stats',
|
||
totalRecords: records.length,
|
||
stats: {
|
||
enrolled: records.length,
|
||
completed: records.filter((r: any) => r.complete === '2').length,
|
||
dataQuality: '87.5%' // TODO: 摰鮋<E691B0>霈∠<E99C88>
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
case 'patient_detail': {
|
||
// <20>亥砭<E4BAA5>孵<EFBFBD><E5ADB5><EFBFBD><EFBFBD>? if (!args.patient_id) {
|
||
return {
|
||
success: false,
|
||
data: null,
|
||
error: '蝻箏<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>蝝Y䰻霂<E4B0BB><E99C82>
|
||
*/
|
||
private async executeSearchKnowledge(
|
||
args: {
|
||
query: string;
|
||
doc_category?: 'protocol' | 'crf' | 'report';
|
||
},
|
||
context: { projectId: string; userId: string }
|
||
): Promise<ToolExecutionResult> {
|
||
// 1. <20>嘥<EFBFBD><E598A5>靝ifyAdapter
|
||
const dify = new DifyAdapter(context.projectId);
|
||
|
||
// 2. <20>𦦵揣<F0A6A6B5>亥<EFBFBD>摨? const result = await dify.searchKnowledge(args.query, {
|
||
doc_type: args.doc_category,
|
||
top_k: 3
|
||
});
|
||
|
||
if (!result.success) {
|
||
return {
|
||
success: false,
|
||
data: null,
|
||
error: result.error || '<27>亥<EFBFBD>摨𤘪<E691A8>蝝W仃韐?
|
||
};
|
||
}
|
||
|
||
// 3. <20>澆<EFBFBD><E6BE86>𣇉<EFBFBD><F0A38789>? return {
|
||
success: true,
|
||
data: {
|
||
type: 'knowledge_search',
|
||
query: args.query,
|
||
category: args.doc_category,
|
||
results: result.records.map(record => ({
|
||
content: record.content,
|
||
score: record.score,
|
||
metadata: record.metadata
|
||
}))
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 撌亙<E6928C><E4BA99>扯<EFBFBD>蝏𤘪<E89D8F>
|
||
*/
|
||
export interface ToolExecutionResult {
|
||
success: boolean;
|
||
data: any;
|
||
error?: string;
|
||
}
|
||
```
|
||
|
||
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?ToolExecutor蝐餃<E89D90><E9A483>啣<EFBFBD><E595A3>?- <20>?query_clinical_data撌亙<E6928C><E4BA99>舀<EFBFBD>銵?- <20>?search_knowledge_base撌亙<E6928C><E4BA99>舀<EFBFBD>銵?- <20>?<3F>躰秤憭<E7A7A4><E686AD>摰<EFBFBD><E691B0>
|
||
|
||
---
|
||
|
||
### Day 3嚗𡁻<E59A97><F0A181BB>𣂷<EFBFBD>銝𡁜凝靽∪笆霂嘅<E99C82>8撠𤩺𧒄嚗?
|
||
#### 隞餃𦛚3.1嚗𡁜<EFBFBD>撘斡echatCallbackController嚗?撠𤩺𧒄嚗?
|
||
**靽格㺿<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>V蛹<EFBCB6>冽<EFBFBD><E586BD>见末<E8A781><E69CAB><EFBFBD>蝑? */
|
||
export class AnswerGenerator {
|
||
private llm: OpenAI;
|
||
|
||
constructor() {
|
||
this.llm = new OpenAI({
|
||
apiKey: process.env.OPENAI_API_KEY,
|
||
baseURL: process.env.OPENAI_BASE_URL || 'https://api.deepseek.com'
|
||
});
|
||
}
|
||
|
||
/**
|
||
* <20><><EFBFBD><EFBFBD>䂿<EFBFBD>
|
||
*/
|
||
async generate(
|
||
userQuestion: string,
|
||
toolResult: ToolExecutionResult,
|
||
toolName: string
|
||
): Promise<string> {
|
||
try {
|
||
logger.info('[AnswerGenerator] Generating answer', {
|
||
question: userQuestion.substring(0, 50),
|
||
toolName,
|
||
success: toolResult.success
|
||
});
|
||
|
||
// 憒<><E68692>撌亙<E6928C><E4BA99>扯<EFBFBD>憭梯揖
|
||
if (!toolResult.success) {
|
||
return this.generateErrorMessage(toolResult.error);
|
||
}
|
||
|
||
// <20>寞旿銝滚<E98A9D>撌亙<E6928C>蝐餃<E89D90>嚗䔶蝙<E494B6>其<EFBFBD><E585B6>𣬚<EFBFBD><F0A3AC9A>䂿<EFBFBD>璅⊥踎
|
||
if (toolName === 'query_clinical_data') {
|
||
return this.generateDataAnswer(userQuestion, toolResult.data);
|
||
} else if (toolName === 'search_knowledge_base') {
|
||
return await this.generateKnowledgeAnswer(userQuestion, toolResult.data);
|
||
}
|
||
|
||
return '<27>望<EFBFBD>嚗峕<E59A97><E5B395>䭾<EFBFBD><E4ADBE><EFBFBD><EFBFBD><EFBFBD>䂿<EFBFBD>';
|
||
} catch (error: any) {
|
||
logger.error('[AnswerGenerator] Generation failed', {
|
||
error: error.message
|
||
});
|
||
|
||
return '<27>望<EFBFBD>嚗<EFBFBD><E59A97>蝑𠉛<E89D91><F0A0899B>𣂼仃韐伐<E99F90>霂瑞<E99C82><E7919E>𤾸<EFBFBD>霂?;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* <20><><EFBFBD><EFBFBD>唳旿<E594B3>亥砭<E4BAA5><E7A0AD><EFBFBD>蝑䈑<E89D91>雿輻鍂璅⊥踎嚗䔶<E59A97>靚<EFBFBD>鍂LLM嚗? */
|
||
private generateDataAnswer(question: string, data: any): string {
|
||
const type = data.type;
|
||
|
||
if (type === 'project_stats') {
|
||
return `<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>蝝Y<E89D9D><EFBCB9>䂿<EFBFBD>嚗<EFBFBD><E59A97><EFBFBD>汪LM蝏澆<E89D8F>嚗? */
|
||
private async generateKnowledgeAnswer(question: string, data: any): Promise<string> {
|
||
const results = data.results;
|
||
|
||
if (results.length === 0) {
|
||
return `<EFBFBD><EFBFBD> **<EFBFBD>亥<EFBFBD>摨𤘪<EFBFBD>蝝?*
|
||
|
||
<EFBFBD>?<3F>芣𪄳<E88AA3>啁㮾<E59581>喳<EFBFBD>摰?
|
||
<EFBFBD>働 撱箄悅嚗?1. 撠肽<E692A0><E882BD>V葵<EFBCB6>喲睸霂?2. <20>亦<EFBFBD><E4BAA6>𠉛弦<F0A0899B>寞<EFBFBD><E5AF9E><EFBFBD><EFBFBD>
|
||
3. <20>𠉛頂憿寧𤌍<E5AFA7>讛<EFBFBD><E8AE9B>𧜶;
|
||
}
|
||
|
||
// 撠<><E692A0>蝝Y<E89D9D><EFBCB9>𨀣𣄽<F0A880A3>乩蛹銝𠹺<E98A9D><F0A0B9BA>? const context = results
|
||
.map((r: any, index: number) => `[<5B><>﹝${index + 1}] ${r.content}`)
|
||
.join('\n\n');
|
||
|
||
// 靚<>鍂LLM蝏澆<E89D8F><E6BE86>䂿<EFBFBD>
|
||
const response = await this.llm.chat.completions.create({
|
||
model: 'deepseek-chat',
|
||
messages: [
|
||
{
|
||
role: 'system',
|
||
content: `雿䭾糓銝游<E98A9D><E6B8B8>𠉛弦<F0A0899B>拇<EFBFBD><E68B87><EFBFBD>覔<EFBFBD>格<EFBFBD>蝝W<E89D9D><EFBCB7><EFBFBD><EFBFBD>獢<EFBFBD><E78DA2>摰對<E691B0><E5B08D>䂿<EFBFBD><E482BF>冽<EFBFBD><E586BD>桅<EFBFBD><E6A185>?閬<><E996AC>嚗?1. <20>䂿<EFBFBD>閬<EFBFBD><E996AC>蝖柴<E89D96><E69FB4><EFBFBD>瘣?2. 撘閧鍂<E996A7><E98D82>﹝<EFBFBD>嗆<EFBFBD>瘜沍<E7989C><E6B28D>﹝X]
|
||
3. 憒<><E68692><EFBFBD><EFBFBD>﹝銝剜瓷<E5899C>㗇<EFBFBD>蝖桃<E89D96>獢<EFBFBD><E78DA2>霂𡁜<E99C82>霂湔<E99C82>
|
||
4. 雿輻鍂Markdown<77>澆<EFBFBD>`
|
||
},
|
||
{
|
||
role: 'user',
|
||
content: `<60>冽<EFBFBD><E586BD>桅<EFBFBD>嚗?{question}\n\n璉<6E>蝝W<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>摨𤘪䰻霂Y<E99C82><EFBCB9>?*\n\n${answer}`;
|
||
}
|
||
|
||
/**
|
||
* <20><><EFBFBD><EFBFBD>躰秤<E8BAB0>鞟內
|
||
*/
|
||
private generateErrorMessage(error?: string): string {
|
||
return `<60>?<3F>亥砭憭梯揖
|
||
|
||
<EFBFBD>笔<EFBFBD>嚗?{error || '<27>芰䰻<E88AB0>躰秤'}
|
||
|
||
<EFBFBD>働 <20>典虾隞伐<E99A9E>
|
||
1. 蝔滚<E89D94><E6BB9A>滩<EFBFBD>
|
||
2. <20>V葵<EFBCB6>格<EFBFBD>
|
||
3. <20>𠉛頂蝞∠<E89D9E><E288A0>𧜶;
|
||
}
|
||
}
|
||
```
|
||
|
||
**撉峕𤣰<E5B395><F0A4A3B0><EFBFBD>**嚗?- <20>?<3F>舐<EFBFBD><E88890>鞉㺭<E99E89>格䰻霂W<E99C82>蝑?- <20>?<3F>舐<EFBFBD><E88890>鞟䰻霂<E4B0BB><E99C82>蝝W<E89D9D>蝑?- <20>?<3F>䂿<EFBFBD><E482BF>澆<EFBFBD><E6BE86>见末嚗㇈arkdown嚗?- <20>?<3F>躰秤<E8BAB0>鞟內皜<E585A7>苊
|
||
|
||
---
|
||
|
||
#### 隞餃𦛚3.3嚗𡁶垢<EFBFBD>啁垢瘚贝<EFBFBD>嚗?撠𤩺𧒄嚗?
|
||
**瘚贝<E7989A><E8B49D>箸艶**嚗?
|
||
```typescript
|
||
// 瘚贝<E7989A><E8B49D>箸艶1嚗𡁏䰻霂a★<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嚗𡁏䰻霂Y鸌摰𡁏<E691B0><F0A1818F>?{
|
||
input: "P001<30><31><EFBFBD><EFBFBD><EFBFBD>摰峕㺭<E5B395>桐<EFBFBD><E6A190>梹<EFBFBD>",
|
||
expectedTool: "query_clinical_data",
|
||
expectedIntent: "patient_detail",
|
||
expectedOutput: "<22>𪈠 <20><><EFBFBD>?P001 霂行<E99C82>"
|
||
}
|
||
|
||
// 瘚贝<E7989A><E8B49D>箸艶3嚗𡁏䰻霂Y<E99C82>蝛嗆䲮獢?{
|
||
input: "<22>交<EFBFBD><E4BAA4><EFBFBD><EFBFBD><EFBFBD>臭<EFBFBD>銋<EFBFBD><E98A8B>",
|
||
expectedTool: "search_knowledge_base",
|
||
expectedCategory: "protocol",
|
||
expectedOutput: "<22><> <20>亥<EFBFBD>摨𤘪䰻霂Y<E99C82><EFBCB9>?
|
||
}
|
||
|
||
// 瘚贝<E7989A><E8B49D>箸艶4嚗𡁏䰻霂$RF銵冽聢
|
||
{
|
||
input: "BMI餈嗘葵摮埈挾<E59F88>𦒘<EFBFBD>憛恬<E6869B>",
|
||
expectedTool: "search_knowledge_base",
|
||
expectedCategory: "crf",
|
||
expectedOutput: "<22><> <20>亥<EFBFBD>摨𤘪䰻霂Y<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>䰻霂Y掩<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>蝝Y<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>蝝W<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><>䰻霂a<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>W<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>Y<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>蝝W歇摰䂿緵**嚗鑹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>蝝Y<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>蝝X沲<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>蝝g<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>?
|