Spaces:
Sleeping
Sleeping
ใ
ใ
ใ
commited on
Commit
ยท
ea80cdc
0
Parent(s):
Initial commit for Hugging Face Spaces
Browse files- .gitignore +34 -0
- ARCHITECTURE.md +231 -0
- CodeWeaver +1 -0
- DYNAMIC_PARALLEL_SEARCH.md +553 -0
- README.md +40 -0
- app.py +41 -0
- requirements.txt +7 -0
.gitignore
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
*.egg-info/
|
| 8 |
+
dist/
|
| 9 |
+
build/
|
| 10 |
+
|
| 11 |
+
# Environment
|
| 12 |
+
.env
|
| 13 |
+
.venv
|
| 14 |
+
env/
|
| 15 |
+
venv/
|
| 16 |
+
ENV/
|
| 17 |
+
|
| 18 |
+
# IDE
|
| 19 |
+
.vscode/
|
| 20 |
+
.idea/
|
| 21 |
+
*.swp
|
| 22 |
+
*.swo
|
| 23 |
+
|
| 24 |
+
# OS
|
| 25 |
+
.DS_Store
|
| 26 |
+
Thumbs.db
|
| 27 |
+
|
| 28 |
+
# Logs
|
| 29 |
+
*.log
|
| 30 |
+
|
| 31 |
+
# Lock files (HF Spaces will install from requirements.txt)
|
| 32 |
+
uv.lock
|
| 33 |
+
poetry.lock
|
| 34 |
+
|
ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CodeWeaver ์ํคํ
์ฒ (์ค์ ์ฝ๋ ๊ธฐ์ค)
|
| 2 |
+
|
| 3 |
+
์ด ๋ฌธ์๋ ํ์ฌ ์ ์ฅ์์ CodeWeaver๊ฐ **์ด๋ค ์์๋ก ๋์ํ๋์ง**, ๊ทธ๋ฆฌ๊ณ ๊ทธ ์๋ฆฌ๊ฐ ๋ฌด์์ธ์ง(์ํ/๋ผ์ฐํ
/๋ณ๋ ฌํ/์บ์)๋ฅผ **์ฝ๋์ 1:1๋ก ์ ํฉ**๋๊ฒ ์ค๋ช
ํฉ๋๋ค.
|
| 4 |
+
|
| 5 |
+
## ์ ์ฒด ๊ตฌ์ฑ ์์
|
| 6 |
+
|
| 7 |
+
- **UI**: Gradio ์ฑํ
UI (`CodeWeaver/ui/app.py`)
|
| 8 |
+
- ์ฌ์ฉ์ ์
๋ ฅ์ `AgentState`๋ก ํฌ์ฅํ ๋ค `agent.ainvoke(..., config={"configurable": {"thread_id": ...}})`๋ก ์คํํฉ๋๋ค.
|
| 9 |
+
- **์ค์ผ์คํธ๋ ์ด์
(๊ทธ๋ํ)**: LangGraph `StateGraph` (`CodeWeaver/src/agent/graph.py`)
|
| 10 |
+
- `START โ create_plan`๋ก ์ง์
ํ, ์ง๋ฌธ ์ ํ/๊ฐ์์ ๋ฐ๋ผ ๋ถ๊ธฐํฉ๋๋ค.
|
| 11 |
+
- ์ฒดํฌํฌ์ธํ
: `MemorySaver` ์ฌ์ฉ(์ค๋ ๋/์ธ์
๋จ์ ์ํ ์ ์ง).
|
| 12 |
+
- **๋
ธ๋ ๊ตฌํ**: (`CodeWeaver/src/agent/nodes.py`)
|
| 13 |
+
- ์ง๋ฌธ ๋ถ์, ์บ์ ์กฐํ, ์๋ ๋ถ๋ฅ, 3์์ค ๋ณ๋ ฌ ๊ฒ์, ๊ฒฐ๊ณผ ํ๊ฐ/๋ฆฌํ์ธ, ํํฐ๋ง/์์ฝ, ๋ต๋ณ ์์ฑ, ๋ค์ค ์ง๋ฌธ ๊ฒฐํฉ ๋ฑ์ ๋ด๋นํฉ๋๋ค.
|
| 14 |
+
- **์ํ ๋ชจ๋ธ(Reducer ํฌํจ)**: (`CodeWeaver/src/agent/state.py`)
|
| 15 |
+
- `search_results`๋ `Annotated[List[SearchResult], add]`๋ก **๋ณ๋ ฌ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์๋ ๋ณํฉ**๋ฉ๋๋ค.
|
| 16 |
+
- `intermediate_steps`, `multi_answers`๋ **๋ฆฌ์
ํ ํฐ์ ์ง์ํ๋ ์ปค์คํ
reducer**๋ก, ์ฒดํฌํฌ์ธํ
/์ค๋ ๋ ์ ์ง ์ ์ด์ ํด์ ๋์ ์ ๋ฐฉ์งํฉ๋๋ค.
|
| 17 |
+
- **์บ์(Vector DB)**: Qdrant Cloud (`CodeWeaver/src/vector_db/qdrant_client.py`)
|
| 18 |
+
- ์๋ฒ ๋ฉ์ ๋ก์ปฌ `BAAI/bge-m3`(`sentence-transformers`)๋ก ์์ฑ, Qdrant์ ์ ์ฅ/๊ฒ์ํฉ๋๋ค.
|
| 19 |
+
- **๊ฒ์ ์์ค**: (`CodeWeaver/src/tools/search_tools.py`)
|
| 20 |
+
- Stack Overflow(๊ณต์ StackExchange API), GitHub Code Search API, Tavily(๊ณต์๋ฌธ์ ๋๋ฉ์ธ ์ ํ) ์ฌ์ฉ.
|
| 21 |
+
|
| 22 |
+
## ์ฌ์ฉ์ ์ ๊ณต ๊ทธ๋ํ์์ ์ ํฉ์ฑ
|
| 23 |
+
|
| 24 |
+
์ฌ์ฉ์๊ป์ ์ ๊ณตํ Mermaid ๊ทธ๋ํ๋ ์ด ํ๋ก์ ํธ์ ์๋์ **๋๋ถ๋ถ ์ผ์น**ํฉ๋๋ค.
|
| 25 |
+
|
| 26 |
+
### ์ผ์นํ๋ ๋ถ๋ถ(ํต์ฌ ํ์ดํ๋ผ์ธ)
|
| 27 |
+
|
| 28 |
+
- `create_plan`์์ **single_topic / multiple_questions(2๊ฐ) / too_many(3+)** ๋ถ๊ธฐ
|
| 29 |
+
- ๋จ์ผ ์ง๋ฌธ(ํน์ ๋จ์ผ ์ฃผ์ )์์:
|
| 30 |
+
- `analyze_question โ check_cache โ (hit๋ฉด return_cached_answer) / (miss๋ฉด classify_intent)`
|
| 31 |
+
- `classify_intent` ์ดํ 3์์ค ๊ฒ์์ Send API๋ก ๋ณ๋ ฌ ์คํ(fan-out)ํ๊ณ `collect_results`์์ fan-in
|
| 32 |
+
- `evaluate_results โ (ํ์ ์ refine_search 1ํ) โ filter_and_score โ summarize_results โ generate_answer`
|
| 33 |
+
- `evaluate_results`๊ฐ ๋ถ์กฑํ๋ฉด `refine_search โ classify_intent`๋ก **์ต๋ 1ํ ๋ฃจํ**
|
| 34 |
+
|
| 35 |
+
### ์ค์ ์ฝ๋์์ ์ถ๊ฐ/๋ณํ๋ ๋ถ๋ถ(์ค์)
|
| 36 |
+
|
| 37 |
+
1) **clarification(๋ณด์ถฉ ์์ฒญ) ์ ์ฉ ๊ฒฝ๋ก๊ฐ ์กด์ฌ**
|
| 38 |
+
|
| 39 |
+
- `analyze_question` ๊ฒฐ๊ณผ๊ฐ `clarification`์ด๋ฉด
|
| 40 |
+
- **์บ์/๊ฒ์์ ์ํํ์ง ์๊ณ **
|
| 41 |
+
- `generate_with_history`๋ก ๋ฐ๋ก ๋ต๋ณํ๊ณ ์ข
๋ฃํฉ๋๋ค.
|
| 42 |
+
|
| 43 |
+
2) **multiple_questions fan-out์ `analyze_question`๋ก ์ง์ ๋ค์ด๊ฐ์ง ์์**
|
| 44 |
+
|
| 45 |
+
์ฌ์ฉ์ ๊ทธ๋ํ๋ โdynamic์์ Send๋ก analyze_question์ 2๋ฒ ํธ์ถโ ํํ์ ๊ฐ๊น์ง๋ง, ์ค์ ๊ตฌํ์ ๋ค๋ฆ
๋๋ค.
|
| 46 |
+
|
| 47 |
+
- ์ค์ ๊ตฌํ์ `fanout_multi_questions`๊ฐ `Send("run_single_question_worker", child_state)`๋ฅผ ์์ฑํฉ๋๋ค.
|
| 48 |
+
- ์ด์ : outer graph์์ ์ง๋ฌธ 2๊ฐ๋ฅผ ๋์์ ๋์ผ ํ์ดํ๋ผ์ธ(analyze/cache/intent/โฆ)์ผ๋ก ๋๋ฆฌ๋ฉด
|
| 49 |
+
- `question_type`, `cached_result` ๊ฐ์ **scalar ์ฑ๋(state ํ๋)**์ด ๋ณ๋ ฌ ์
๋ฐ์ดํธ ์ถฉ๋์ ์ผ์ผํฌ ์ ์์ต๋๋ค.
|
| 50 |
+
- ๋ฐ๋ผ์ **worker ๋ด๋ถ์์ ๋ณ๋์ โ๋จ์ผ ์ง๋ฌธ ๊ทธ๋ํโ๋ฅผ ์คํ**ํ๊ณ ,
|
| 51 |
+
- outer graph์๋ reducer ์ฑ๋์ธ `multi_answers`๋ง ์
๋ฐ์ดํธํ์ฌ ์ถฉ๋์ ์ ๊ฑฐํฉ๋๋ค.
|
| 52 |
+
|
| 53 |
+
## ์ค์ ์คํ ํ๋ฆ(์ฝ๋ ๊ธฐ์ค)
|
| 54 |
+
|
| 55 |
+
### 1) UI โ Agent ์คํ(์ํธ๋ฆฌ)
|
| 56 |
+
|
| 57 |
+
`CodeWeaver/ui/app.py`์์:
|
| 58 |
+
|
| 59 |
+
- ์
๋ ฅ ๋ฌธ์์ด `message`๋ฅผ `AgentState(user_question=..., messages=[HumanMessage(...)], ...)`๋ก ๋ง๋ค๊ณ
|
| 60 |
+
- `thread_id`๋ฅผ `config={"configurable":{"thread_id": thread_id}}`๋ก ์ ๋ฌํ์ฌ `agent.ainvoke()` ์คํ
|
| 61 |
+
- `MemorySaver`๊ฐ `thread_id` ๋จ์๋ก ์ํ๋ฅผ ๋ณด์กดํฉ๋๋ค.
|
| 62 |
+
|
| 63 |
+
### 2) ๋ฉ์ธ ๊ทธ๋ํ(Top-level) ํ๋ฆ
|
| 64 |
+
|
| 65 |
+
`CodeWeaver/src/agent/graph.py` ๊ธฐ์ค ๋ฉ์ธ ํ๋ฆ์ ์๋์ ๊ฐ์ต๋๋ค.
|
| 66 |
+
|
| 67 |
+
```mermaid
|
| 68 |
+
graph TD
|
| 69 |
+
startNode[START] --> createPlan[create_plan]
|
| 70 |
+
|
| 71 |
+
createPlan -->|single_topic| analyzeQuestion[analyze_question]
|
| 72 |
+
createPlan -->|multiple_questions_2| initiateDynamic[initiate_dynamic_search]
|
| 73 |
+
createPlan -->|too_many_3plus| tooMany[handle_too_many_questions]
|
| 74 |
+
|
| 75 |
+
tooMany --> endNode[END]
|
| 76 |
+
|
| 77 |
+
analyzeQuestion -->|clarification| withHistory[generate_with_history]
|
| 78 |
+
withHistory --> endNode
|
| 79 |
+
|
| 80 |
+
analyzeQuestion -->|new_topic_or_independent| checkCache[check_cache]
|
| 81 |
+
checkCache -->|hit| returnCached[return_cached_answer]
|
| 82 |
+
returnCached --> endNode
|
| 83 |
+
|
| 84 |
+
checkCache -->|miss| classifyIntent[classify_intent]
|
| 85 |
+
|
| 86 |
+
classifyIntent --> searchSO[search_stackoverflow]
|
| 87 |
+
classifyIntent --> searchGH[search_github]
|
| 88 |
+
classifyIntent --> searchDocs[search_official_docs]
|
| 89 |
+
|
| 90 |
+
searchSO --> collect[collect_results]
|
| 91 |
+
searchGH --> collect
|
| 92 |
+
searchDocs --> collect
|
| 93 |
+
|
| 94 |
+
collect --> evalNode[evaluate_results]
|
| 95 |
+
evalNode -->|needs_refinement_and_lt1| refine[refine_search]
|
| 96 |
+
refine --> classifyIntent
|
| 97 |
+
|
| 98 |
+
evalNode -->|sufficient_or_ge1| searchSubgraph[search_subgraph]
|
| 99 |
+
searchSubgraph --> generateAnswer[generate_answer]
|
| 100 |
+
generateAnswer --> routeAfterGen[route_after_generate]
|
| 101 |
+
routeAfterGen -->|single| endNode
|
| 102 |
+
routeAfterGen -->|multi| combine[combine_answers]
|
| 103 |
+
combine --> endNode
|
| 104 |
+
|
| 105 |
+
initiateDynamic --> fanout[fanout_multi_questions]
|
| 106 |
+
fanout --> worker[run_single_question_worker]
|
| 107 |
+
worker --> combine
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
### 3) `create_plan`: ์ง๋ฌธ ๊ฐ์/ํํ ํ๋ณ + โ3๊ฐ ์ด์โ ํ๋ ๊ฐ๋
|
| 111 |
+
|
| 112 |
+
`create_plan_node`๋ ์
๋ ฅ์ ์๋ 3๊ฐ์ง๋ก ๋ถ๋ฅํฉ๋๋ค.
|
| 113 |
+
|
| 114 |
+
- **single_topic**: ํ๋์ ์ฃผ์ ๋ฅผ ๋ค์ํ ๊ด์ ์ผ๋ก ๋ฌป๋ ํํ
|
| 115 |
+
- **multiple_questions**: ๋
๋ฆฝ ์ง๋ฌธ 2๊ฐ
|
| 116 |
+
- **too_many**: ๋
๋ฆฝ ์ง๋ฌธ 3๊ฐ ์ด์
|
| 117 |
+
|
| 118 |
+
์ถ๊ฐ๋ก, LLM ๋ถ๋ฅ์ ๋ฌด๊ดํ๊ฒ ๋ค์ ์กฐ๊ฑด์ด๋ฉด **๊ฒฐ์ ๋ก ์ ์ผ๋ก too_many**๋ก ๊ฐ์ ํฉ๋๋ค.
|
| 119 |
+
|
| 120 |
+
- ๋ฌผ์ํ๊ฐ 3๊ฐ ์ด์
|
| 121 |
+
- ๋๋ โ์ง๋ฌธ ํ๋ณดโ๊ฐ 3๊ฐ ์ด์(์ค๋ฐ๊ฟ/๋ฒํธ/๊ตฌ๋ถ์ ๋ฑ์ผ๋ก ์ถ์ )
|
| 122 |
+
|
| 123 |
+
๋ํ ์ฒดํฌํฌ์ธํ
์ํ ๋์ ์ ๋ง๊ธฐ ์ํด, ๋งค ์คํ ์์ ์ `multi_answers`๋ฅผ ๋ฆฌ์
ํ ํฐ์ผ๋ก ์ด๊ธฐํํฉ๋๋ค.
|
| 124 |
+
|
| 125 |
+
### 4) `analyze_question`: ์ง๋ฌธ ํ์
(clarification/new_topic/independent) + ์บ์ ์ ๊ฒฉ์ฑ ํ๋จ
|
| 126 |
+
|
| 127 |
+
`analyze_question_node`๊ฐ LLM์ผ๋ก ์๋ ๊ฐ์ ์์ฑํฉ๋๋ค.
|
| 128 |
+
|
| 129 |
+
- `question_type`: `clarification | new_topic | independent`
|
| 130 |
+
- `should_cache`: ์บ์ ์ ์ฅ ์ฌ๋ถ
|
| 131 |
+
- `canonical_question`: ์บ์์ฉ ์ ๊ทํ ์ง๋ฌธ(should_cache=true์ผ ๋)
|
| 132 |
+
|
| 133 |
+
๋ผ์ฐํ
์ `graph.py`์ `route_after_analysis`์์:
|
| 134 |
+
|
| 135 |
+
- `clarification` โ `generate_with_history` (๊ฒ์/์บ์ ์๋ต)
|
| 136 |
+
- ๋๋จธ์ง โ `check_cache`
|
| 137 |
+
|
| 138 |
+
### 5) ์บ์(`check_cache` / `return_cached_answer`)
|
| 139 |
+
|
| 140 |
+
`check_cache_node`๋ Qdrant์์ ์ ์ฌ ์ง๋ฌธ์ ๊ฒ์ํฉ๋๋ค.
|
| 141 |
+
|
| 142 |
+
- ์๋ฒ ๋ฉ: ๋ก์ปฌ `BAAI/bge-m3` (1024์ฐจ์)
|
| 143 |
+
- ์๊ณ๊ฐ: cosine score **0.85 ์ด์**์ด๋ฉด hit๋ก ๊ฐ์ฃผ
|
| 144 |
+
|
| 145 |
+
hit๋ฉด `return_cached_answer_node`๊ฐ ์ ์ฅ๋ ๋ต๋ณ์ ์ฆ์ ๋ฐํํฉ๋๋ค.
|
| 146 |
+
|
| 147 |
+
### 6) ์๋ ๋ถ๋ฅ(`classify_intent`)
|
| 148 |
+
|
| 149 |
+
`classify_intent_node`๊ฐ ์ง๋ฌธ์ `debugging | learning | code_review`๋ก ๋ถ๋ฅํฉ๋๋ค.
|
| 150 |
+
|
| 151 |
+
์ด ๊ฐ์ ๊ฒ์ ๊ฐ์ ๋ฑ ์ผ๋ถ ์ ์ฑ
์ ๋ฐ์๋ฉ๋๋ค(์: StackOverflow๋ debugging์ด๋ฉด ๋ ๋ง์ด ๊ฐ์ ธ์ด).
|
| 152 |
+
|
| 153 |
+
### 7) ๋ณ๋ ฌ ๊ฒ์(fan-out) โ ์์ง(fan-in)
|
| 154 |
+
|
| 155 |
+
`classify_intent` ์ดํ conditional edge ํจ์๊ฐ `Send(...)` 3๊ฐ๋ฅผ ๋ฐํํ์ฌ ๋ณ๋ ฌ๋ก ์คํ๋ฉ๋๋ค.
|
| 156 |
+
|
| 157 |
+
- `search_stackoverflow_node`
|
| 158 |
+
- `search_github_node`
|
| 159 |
+
- `search_official_docs_node`
|
| 160 |
+
|
| 161 |
+
๊ฐ ๋
ธ๋๋ `{"search_results": [..]}`๋ฅผ ๋ฐํํ๊ณ , `AgentState.search_results`์ reducer(`add`)๊ฐ ์ด๋ฅผ ์๋ ๋ณํฉํฉ๋๋ค.
|
| 162 |
+
|
| 163 |
+
`collect_results_node`๋ ๋ณํฉ๋ ์ด ๊ฒฐ๊ณผ ๊ฐ์๋ง ์ง๊ณํฉ๋๋ค.
|
| 164 |
+
|
| 165 |
+
### 8) ๊ฒฐ๊ณผ ํ๊ฐ(`evaluate_results`)์ ์ฟผ๋ฆฌ ๋ฆฌํ์ธ(`refine_search`)
|
| 166 |
+
|
| 167 |
+
`evaluate_results_node`๋ ๋ค์ ๊ธฐ์ค์ผ๋ก โ๊ฐ์ ํ์โ๋ฅผ ํ๋จํฉ๋๋ค.
|
| 168 |
+
|
| 169 |
+
- ๊ฒฐ๊ณผ ๊ฐ์ < 2 โ ๊ฐ์ ํ์
|
| 170 |
+
- (relevance_score๊ฐ ์๋ค๋ฉด) ํ๊ท ์ ์ < 0.5 โ ๊ฐ์ ํ์
|
| 171 |
+
|
| 172 |
+
`refine_search_node`๋ LLM์ด `MORE_SPECIFIC | MORE_GENERAL | TRANSLATE` ์ ๋ต์ ์ ํํด ์ฟผ๋ฆฌ๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 173 |
+
|
| 174 |
+
- ๋ฌดํ ๋ฃจํ ๋ฐฉ์ง: `refinement_count < 1`์ผ ๋๋ง 1ํ ํ์ฉ
|
| 175 |
+
- ์ฌ๊ฒ์์ ์ํด `search_results`๋ฅผ ๋น ๋ฆฌ์คํธ๋ก ์ด๊ธฐํํ๊ณ `classify_intent`๋ก ๋๋์๊ฐ๋๋ค.
|
| 176 |
+
|
| 177 |
+
### 9) `search_subgraph`: ํํฐ๋ง + ์์ฝ
|
| 178 |
+
|
| 179 |
+
๋ฉ์ธ ๊ทธ๋ํ์๋ `search_subgraph`๊ฐ โํ๋์ ๋
ธ๋โ์ฒ๋ผ ๋ถ์ด ์์ต๋๋ค.
|
| 180 |
+
|
| 181 |
+
- `filter_and_score`: ์ต์ ๊ธธ์ด/URL ์กฐ๊ฑด์ผ๋ก ํํฐ ํ, ์์ ์ผ๋ถ์ ๋ํด ๊ด๋ จ๋ ์ ์ ๋ถ์ฌ
|
| 182 |
+
- `summarize_results`: ๊ฐ ๊ฒฐ๊ณผ๋ฅผ 2~3๋ฌธ์ฅ์ผ๋ก ์์ฝ
|
| 183 |
+
|
| 184 |
+
### 10) `generate_answer`: ๋ต๋ณ ์์ฑ + (์กฐ๊ฑด๋ถ) ์บ์ ์ ์ฅ
|
| 185 |
+
|
| 186 |
+
`generate_answer_node`๋ ์๋์ ๋ฐ๋ผ ํ
ํ๋ฆฟ์ ๋ฐ๊ฟ ์ต์ข
๋ต๋ณ์ ์์ฑํฉ๋๋ค.
|
| 187 |
+
|
| 188 |
+
์บ์ ์ ์ฅ ์ ์ฑ
:
|
| 189 |
+
|
| 190 |
+
- `question_type`๊ฐ `new_topic` ๋๋ `independent`์ด๊ณ `should_cache`๊ฐ true์ด๋ฉด ์ ์ฅ
|
| 191 |
+
- `clarification`์ ์ ์ฅํ์ง ์์(๋ผ์ฐํ
์ ๋ณดํต ์ฌ๊ธฐ๋ก ์ค์ง ์์ง๋ง ๋ฐฉ์ด์ ์ผ๋ก ์ฒดํฌ)
|
| 192 |
+
|
| 193 |
+
### 11) ๋ค์ค ์ง๋ฌธ(multiple_questions) ์ฒ๋ฆฌ ์๋ฆฌ
|
| 194 |
+
|
| 195 |
+
๋ค์ค ์ง๋ฌธ์ ํต์ฌ์ โouter graph๋ ์ถฉ๋ ์์ด orchestration๋ง, ์ค์ ํ์ดํ๋ผ์ธ์ worker ๋ด๋ถ์์ ์คํโ์
๋๋ค.
|
| 196 |
+
|
| 197 |
+
#### ํ๋ฆ
|
| 198 |
+
|
| 199 |
+
- `create_plan(case=multiple_questions)` โ `initiate_dynamic_search` (์ค๋น)
|
| 200 |
+
- `fanout_multi_questions`(conditional edge)์ด ์ง๋ฌธ 2๊ฐ๋ฅผ ๊ฐ๊ฐ `run_single_question_worker`๋ก Send
|
| 201 |
+
- `run_single_question_worker_node` ๋ด๋ถ์์ **๋จ์ผ ์ง๋ฌธ์ฉ ๊ทธ๋ํ๋ฅผ ๋ณ๋ compile/์คํ**
|
| 202 |
+
- worker ๊ฒฐ๊ณผ๋ `multi_answers`์ append(reducer๋ก ๋ณํฉ)
|
| 203 |
+
- ๋ชจ๋ worker๊ฐ ๋๋๋ฉด `combine_answers_node`๊ฐ Markdown์ผ๋ก ๊ฒฐํฉ
|
| 204 |
+
|
| 205 |
+
#### ์ worker๊ฐ ํ์ํ๊ฐ?
|
| 206 |
+
|
| 207 |
+
outer graph์์ ๋์ผํ state๋ฅผ ๋ณต์ ํด `analyze_question`๋ถํฐ ๋์์ ๋๋ฆฌ๋ฉด,
|
| 208 |
+
scalar ์ฑ๋(`question_type`, `cached_result` ๋ฑ)์ด ์๋ก ๋ฎ์ด์ฐ์ผ ์ ์์ต๋๋ค.
|
| 209 |
+
|
| 210 |
+
๊ทธ๋์ ์ค์ ๊ตฌํ์:
|
| 211 |
+
|
| 212 |
+
- worker ๋ด๋ถ์์ ๋จ์ผ ์ง๋ฌธ ๊ทธ๋ํ๋ฅผ ๋๋ฆฌ๊ณ
|
| 213 |
+
- outer state์๋ **reducer ์ฑ๋์ธ `multi_answers`๋ง** ์
๋ฐ์ดํธ
|
| 214 |
+
|
| 215 |
+
์ด ๋ฐฉ์์ผ๋ก ๋ณ๋ ฌ ์คํ ์์ ์ฑ์ ํ๋ณดํฉ๋๋ค.
|
| 216 |
+
|
| 217 |
+
## ํ๊ฒฝ ๋ณ์(์คํ์ ํ์ํ ์ค์ ๊ฐ)
|
| 218 |
+
|
| 219 |
+
ํ์:
|
| 220 |
+
|
| 221 |
+
- `GOOGLE_API_KEY`: Gemini ํธ์ถ(`langchain-google-genai`)
|
| 222 |
+
- `QDRANT_URL`, `QDRANT_API_KEY`: Qdrant Cloud ์บ์
|
| 223 |
+
- `TAVILY_API_KEY`: ๊ณต์ ๋ฌธ์ ๊ฒ์(Tavily)
|
| 224 |
+
|
| 225 |
+
์ ํ:
|
| 226 |
+
|
| 227 |
+
- `GITHUB_TOKEN`: GitHub API rate limit ์ํ(์์ผ๋ฉด 60 req/hr ์์ค)
|
| 228 |
+
- `LANGCHAIN_TRACING_V2`, `LANGCHAIN_API_KEY`: LangSmith ํธ๋ ์ด์ฑ(์ ํ)
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
|
CodeWeaver
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Subproject commit fc4c811e94059981ae4ef7924c9aed6ccc9cbc44
|
DYNAMIC_PARALLEL_SEARCH.md
ADDED
|
@@ -0,0 +1,553 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dynamic Parallel Search for Multiple Independent Questions
|
| 2 |
+
|
| 3 |
+
## ๊ฐ์
|
| 4 |
+
|
| 5 |
+
CodeWeaver Phase 4๋ **๋ค์ค ๋
๋ฆฝ ์ง๋ฌธ**์ Send API๋ก ๋์ ๋ณ๋ ฌ ์ฒ๋ฆฌํ์ฌ, ๊ฐ ์ง๋ฌธ๋ง๋ค ๋
๋ฆฝ์ ์ธ ๊ฒ์ ํ์ดํ๋ผ์ธ์ ์คํํฉ๋๋ค.
|
| 6 |
+
|
| 7 |
+
### ํต์ฌ ์ฒ ํ
|
| 8 |
+
|
| 9 |
+
> "๊ธฐ์กด ๊ทธ๋ํ๋ฅผ 100% ์ฌ์ฌ์ฉํ๋, ์ง๋ฌธ ๊ฐ์๋งํผ ๋ณต์ ํด์ ๋ณ๋ ฌ ์คํํ๋ค"
|
| 10 |
+
|
| 11 |
+
- **๊ธฐ์กด ์ฝ๋ ์ฌ์ฌ์ฉ๋ฅ **: ~95%
|
| 12 |
+
- **์๋ก์ด ๋
ธ๋**: 5๊ฐ ์ถ๊ฐ
|
| 13 |
+
- **์๋ก์ด edge ํจ์**: 1๊ฐ ์ถ๊ฐ (fanout_multi_questions)
|
| 14 |
+
- **์์ ๋ ๋
ธ๋**: 2๊ฐ ์์ (create_plan, generate_answer)
|
| 15 |
+
|
| 16 |
+
## ์ฃผ์ ๊ธฐ๋ฅ
|
| 17 |
+
|
| 18 |
+
### 1. ์๋ ์ง๋ฌธ ์ ํ ๊ฐ์ง
|
| 19 |
+
|
| 20 |
+
**create_plan_node**๊ฐ ์ง๋ฌธ์ ๋ถ์ํ์ฌ 3๊ฐ์ง ์ผ์ด์ค๋ก ๋ถ๋ฅ:
|
| 21 |
+
|
| 22 |
+
#### Case 1: single_topic
|
| 23 |
+
- **์ ์**: ํ๋์ ์ฃผ์ ๋ฅผ ๋ค๊ฐ๋๋ก ๋ฌป๋ ๊ฒฝ์ฐ
|
| 24 |
+
- **์์**: "Spring Security JWT ์ธ์ฆ ๊ตฌํ ๋ฐฉ๋ฒ"
|
| 25 |
+
- **์๋ธ์ง๋ฌธ**: ["๊ฐ๋
", "๊ตฌํ", "์์ "] (๋ต๋ณ ์น์
๊ตฌ์กฐ์ฉ)
|
| 26 |
+
- **์คํ**: ๊ธฐ์กด ๊ทธ๋ํ 1ํ (๊ฒ์์ ์๋ณธ ์ง๋ฌธ์ผ๋ก)
|
| 27 |
+
|
| 28 |
+
#### Case 2: multiple_questions
|
| 29 |
+
- **์ ์**: ์๋ก ๋ฌด๊ดํ ๋
๋ฆฝ ์ง๋ฌธ (์ต๋ 2๊ฐ)
|
| 30 |
+
- **์์**: "JWT๊ฐ ๋ญ์ผ? CORS๋?"
|
| 31 |
+
- **์๋ธ์ง๋ฌธ**: ["JWT๊ฐ ๋ญ์ผ?", "CORS๋?"] (๊ฐ๊ฐ ๋ณ๋ ๊ฒ์)
|
| 32 |
+
- **์คํ**: Send API๋ก ๊ธฐ์กด ๊ทธ๋ํ 2ํ ๋ณ๋ ฌ ์คํ
|
| 33 |
+
|
| 34 |
+
#### Case 3: too_many
|
| 35 |
+
- **์ ์**: ์ง๋ฌธ 3๊ฐ ์ด์
|
| 36 |
+
- **์์**: "JWT? CORS? Docker?"
|
| 37 |
+
- **์คํ**: ์น์ ํ ์๋ฌ ๋ฉ์์ง ํ์, ๋ํ ๊ณ์ ๊ฐ๋ฅ
|
| 38 |
+
- **ํ๋ ๊ฐ๋**: LLM ๋ถ๋ฅ์ ๋ฌด๊ดํ๊ฒ ๋ฌผ์ํ ๊ฐ์(3๊ฐ ์ด์) ๋๋ ์ง๋ฌธ ํ๋ณด ๊ฐ์(3๊ฐ ์ด์)๋ก ๊ฒฐ์ ๋ก ์ ์ฐจ๋จ
|
| 39 |
+
|
| 40 |
+
### 2. ์ง๋ฌธ ๊ฐ์ ์ ํ
|
| 41 |
+
|
| 42 |
+
๋น์ฉ ๋ฐ ํ์ง ๊ด๋ฆฌ๋ฅผ ์ํด **์ต๋ 2๊ฐ ์ง๋ฌธ**์ผ๋ก ์ ํ:
|
| 43 |
+
|
| 44 |
+
```
|
| 45 |
+
์
๋ ฅ: "JWT? CORS? Docker? Redis?"
|
| 46 |
+
์ฒ๋ฆฌ: too_many ์ผ์ด์ค โ ์๋ฌ ๋ฉ์์ง
|
| 47 |
+
์๋ด: "ํ๋์ ์ฃผ์ ๋ก ํตํฉ" ๋๋ "2๊ฐ๋ง ์ ํ" ๊ถ์ฅ
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
### 3. Send API ๋์ ๋ณต์
|
| 51 |
+
|
| 52 |
+
**์ค์**: LangGraph์์ `List[Send]`๋ ๋
ธ๋ ๋ฐํ๊ฐ์ด ์๋๋ผ **conditional edge ํจ์ ๋ฐํ๊ฐ**์ผ๋ก๋ง ์ฌ์ฉ๋ฉ๋๋ค.
|
| 53 |
+
|
| 54 |
+
```python
|
| 55 |
+
# initiate_dynamic_search_node: state ์ค๋น๋ง (dict ๋ฐํ)
|
| 56 |
+
def initiate_dynamic_search_node(state: AgentState) -> dict:
|
| 57 |
+
return {"intermediate_steps": [...]} # Send ๋ฐํ ์ ํจ!
|
| 58 |
+
|
| 59 |
+
# fanout_multi_questions: conditional edge ํจ์ (List[Send] ๋ฐํ)
|
| 60 |
+
def fanout_multi_questions(state: AgentState) -> List[Send]:
|
| 61 |
+
sends = []
|
| 62 |
+
for i, question in enumerate(["JWT๊ฐ ๋ญ์ผ?", "CORS๋?"]):
|
| 63 |
+
child_state = state.model_copy(deep=True)
|
| 64 |
+
child_state.user_question = question
|
| 65 |
+
child_state.is_multi_question = True
|
| 66 |
+
# ... ๋ฉํ๋ฐ์ดํฐ ์ค์ ...
|
| 67 |
+
sends.append(Send("run_single_question_worker", child_state))
|
| 68 |
+
return sends
|
| 69 |
+
|
| 70 |
+
# run_single_question_worker: ๋ด๋ถ ์๋ธ๊ทธ๋ํ ์คํ
|
| 71 |
+
# ๊ฐ Send๋ ๋
๋ฆฝ์ ์ผ๋ก ๋ด๋ถ ๊ทธ๋ํ๋ฅผ ์คํ:
|
| 72 |
+
# analyze โ cache โ classify โ search(ร3) โ collect โ eval โ subgraph โ generate
|
| 73 |
+
# โ multi_answers์ ๊ฒฐ๊ณผ ์ถ๊ฐ
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
### 4. Reducer ์๋ Fan-in (Reset ๊ธฐ๋ฅ ํฌํจ)
|
| 77 |
+
|
| 78 |
+
```python
|
| 79 |
+
# State ์ ์ (์ปค์คํ
reducer ์ฌ์ฉ)
|
| 80 |
+
multi_answers: Annotated[List[Dict[str, Any]], merge_multi_answers] = []
|
| 81 |
+
|
| 82 |
+
# merge_multi_answers reducer:
|
| 83 |
+
# - ๊ธฐ๋ณธ ๋์: old + new (๋ณ๋ ฌ worker์์ ๋ต๋ณ์ ๋์์ append)
|
| 84 |
+
# - ๋ฆฌ์
๋์: new์ ์ฒซ ์์๊ฐ {"__token__": "__RESET_MULTI_ANS__"}์ด๋ฉด
|
| 85 |
+
# old๋ฅผ ๋ฒ๋ฆฌ๊ณ new[1:]๋ก ๊ต์ฒด (์ด์ ํด ๋์ ๋ฐฉ์ง)
|
| 86 |
+
|
| 87 |
+
# run_single_question_worker 1์ด ๋ฆฌํด:
|
| 88 |
+
{"multi_answers": [{"index": 0, "question": "JWT๊ฐ ๋ญ์ผ?", "answer": "..."}]}
|
| 89 |
+
|
| 90 |
+
# run_single_question_worker 2๊ฐ ๋ฆฌํด:
|
| 91 |
+
{"multi_answers": [{"index": 1, "question": "CORS๋?", "answer": "..."}]}
|
| 92 |
+
|
| 93 |
+
# LangGraph Reducer๊ฐ ์๋ ๋ณํฉ:
|
| 94 |
+
state.multi_answers = [
|
| 95 |
+
{"index": 0, ...},
|
| 96 |
+
{"index": 1, ...}
|
| 97 |
+
]
|
| 98 |
+
|
| 99 |
+
# combine_answers_node๊ฐ ์ด๋ฅผ ํตํฉ Markdown์ผ๋ก ๋ณํ
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
## ๊ทธ๋ํ ํ๋ฆ
|
| 103 |
+
|
| 104 |
+
```mermaid
|
| 105 |
+
graph TD
|
| 106 |
+
START[START] --> plan[create_plan]
|
| 107 |
+
|
| 108 |
+
plan -->|single_topic| analyze[analyze_question]
|
| 109 |
+
plan -->|multiple_questions 2๊ฐ| dynamic[initiate_dynamic_search]
|
| 110 |
+
plan -->|too_many 3+| tooMany[handle_too_many_questions]
|
| 111 |
+
|
| 112 |
+
tooMany --> END
|
| 113 |
+
|
| 114 |
+
analyze --> cache[check_cache]
|
| 115 |
+
cache -->|hit| returnCache[return_cached_answer]
|
| 116 |
+
cache -->|miss| classify[classify_intent]
|
| 117 |
+
|
| 118 |
+
returnCache --> END
|
| 119 |
+
|
| 120 |
+
classify --> searchSO[search_stackoverflow]
|
| 121 |
+
classify --> searchGH[search_github]
|
| 122 |
+
classify --> searchDocs[search_official_docs]
|
| 123 |
+
|
| 124 |
+
searchSO --> collect[collect_results]
|
| 125 |
+
searchGH --> collect
|
| 126 |
+
searchDocs --> collect
|
| 127 |
+
|
| 128 |
+
collect --> eval[evaluate_results]
|
| 129 |
+
|
| 130 |
+
eval -->|needs_refinement| refine[refine_search]
|
| 131 |
+
eval -->|sufficient| filterNode[filter_and_score]
|
| 132 |
+
|
| 133 |
+
refine --> classify
|
| 134 |
+
|
| 135 |
+
filterNode --> summarize[summarize_results]
|
| 136 |
+
summarize --> generate[generate_answer]
|
| 137 |
+
|
| 138 |
+
generate -->|is_multi_question| combine[combine_answers]
|
| 139 |
+
generate -->|single_topic| END
|
| 140 |
+
|
| 141 |
+
combine --> END
|
| 142 |
+
|
| 143 |
+
dynamic --> fanout[fanout_multi_questions<br/>conditional edge]
|
| 144 |
+
fanout -.Send Q1.-> worker1[run_single_question_worker<br/>๋ด๋ถ ์๋ธ๊ทธ๋ํ]
|
| 145 |
+
fanout -.Send Q2.-> worker2[run_single_question_worker<br/>๋ด๋ถ ์๋ธ๊ทธ๋ํ]
|
| 146 |
+
worker1 --> combine
|
| 147 |
+
worker2 --> combine
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
### ํ๋ฆ ์ค๋ช
|
| 151 |
+
|
| 152 |
+
#### Single Topic (๊ธฐ์กด ๋์ ์ ์ง)
|
| 153 |
+
```
|
| 154 |
+
START โ create_plan (case: single_topic)
|
| 155 |
+
โ analyze โ cache โ classify โ search(ร3) โ collect โ eval โ subgraph โ generate โ END
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
#### Multiple Questions (์ ๊ท)
|
| 159 |
+
```
|
| 160 |
+
START โ create_plan (case: multiple_questions)
|
| 161 |
+
โ initiate_dynamic_search (state ์ค๋น)
|
| 162 |
+
โ fanout_multi_questions (conditional edge)
|
| 163 |
+
โโ Send("run_single_question_worker", Q1) โ [๋ด๋ถ ์๋ธ๊ทธ๋ํ ์ ์ฒด ํ์ดํ๋ผ์ธ] โ multi_answers[0]
|
| 164 |
+
โโ Send("run_single_question_worker", Q2) โ [๋ด๋ถ ์๋ธ๊ทธ๋ํ ์ ์ฒด ํ์ดํ๋ผ์ธ] โ multi_answers[1]
|
| 165 |
+
โ combine_answers (์๋ fan-in) โ END
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
#### Too Many (์ ๊ท)
|
| 169 |
+
```
|
| 170 |
+
START โ create_plan (case: too_many)
|
| 171 |
+
โ handle_too_many_questions โ END
|
| 172 |
+
(์ฌ์ฉ์๋ ์ฆ์ ๋ค์ ์ง๋ฌธ ๊ฐ๋ฅ)
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
## ๊ตฌํ ์์ธ
|
| 176 |
+
|
| 177 |
+
### State ํ์ฅ
|
| 178 |
+
|
| 179 |
+
```python
|
| 180 |
+
# src/agent/state.py
|
| 181 |
+
|
| 182 |
+
class AgentState(BaseModel):
|
| 183 |
+
# ... ๊ธฐ์กด ํ๋ ...
|
| 184 |
+
|
| 185 |
+
# Phase 4: Dynamic Parallel Search
|
| 186 |
+
is_multi_question: bool = False
|
| 187 |
+
sub_question_index: int = 0
|
| 188 |
+
sub_question_text: Optional[str] = None
|
| 189 |
+
original_multi_question: Optional[str] = None
|
| 190 |
+
multi_answers: Annotated[List[Dict[str, Any]], merge_multi_answers] = []
|
| 191 |
+
```
|
| 192 |
+
|
| 193 |
+
### ์๋ก์ด ๋
ธ๋ (5๊ฐ)
|
| 194 |
+
|
| 195 |
+
#### 1. create_plan_node (์์ )
|
| 196 |
+
- **์์น**: `src/agent/nodes.py` ๋ผ์ธ 206
|
| 197 |
+
- **์ญํ **: ์ง๋ฌธ ์ ํ ๋ฐ ๊ฐ์ ํ๋จ
|
| 198 |
+
- **๋ณ๊ฒฝ**:
|
| 199 |
+
- `case` ํ๋ ์ถ๊ฐ (single_topic/multiple_questions/too_many)
|
| 200 |
+
- **ํ๋ ๊ฐ๋ ์ถ๊ฐ**: `_hard_guard_too_many` ํจ์๋ก 3๊ฐ ์ด์ ์ง๋ฌธ ๊ฒฐ์ ๋ก ์ ์ฐจ๋จ
|
| 201 |
+
- ๋ฌผ์ํ ๊ฐ์(3๊ฐ ์ด์) ๋๋ ์ง๋ฌธ ํ๋ณด ๊ฐ์(3๊ฐ ์ด์) ๊ฐ์ง
|
| 202 |
+
- LLM ๋ถ๋ฅ์ ๋ฌด๊ดํ๊ฒ `too_many`๋ก ๊ฐ์
|
| 203 |
+
|
| 204 |
+
#### 2. handle_too_many_questions_node (์ ๊ท)
|
| 205 |
+
- **์์น**: `src/agent/nodes.py` ๋ผ์ธ 1068
|
| 206 |
+
- **์ญํ **: 3๊ฐ ์ด์ ์ง๋ฌธ ์ ์๋ด ๋ฉ์์ง
|
| 207 |
+
- **ํน์ง**: ๋ํ ์ข
๋ฃํ์ง ์์ (์ฆ์ ์ฌ์ง๋ฌธ ๊ฐ๋ฅ)
|
| 208 |
+
|
| 209 |
+
#### 3. initiate_dynamic_search_node (์ ๊ท)
|
| 210 |
+
- **์์น**: `src/agent/nodes.py` ๋ผ์ธ 1092
|
| 211 |
+
- **์ญํ **: ๋ค์ค ์ง๋ฌธ ์ฒ๋ฆฌ ์ง์
์ , state ์ค๋น
|
| 212 |
+
- **ํต์ฌ**: dict๋ง ๋ฐํ (Send๋ ๋ฐํํ์ง ์์)
|
| 213 |
+
|
| 214 |
+
#### 4. fanout_multi_questions (์ ๊ท - Edge ํจ์)
|
| 215 |
+
- **์์น**: `src/agent/nodes.py` ๋ผ์ธ 1110
|
| 216 |
+
- **์ญํ **: conditional edge ํจ์๋ก `List[Send]` ๋ฐํ
|
| 217 |
+
- **ํต์ฌ**: ๊ฐ ์๋ธ ์ง๋ฌธ์ `run_single_question_worker`๋ก Send
|
| 218 |
+
|
| 219 |
+
#### 5. run_single_question_worker_node (์ ๊ท)
|
| 220 |
+
- **์์น**: `src/agent/nodes.py` ๋ผ์ธ 1306
|
| 221 |
+
- **์ญํ **: ๋ด๋ถ ์๋ธ๊ทธ๋ํ๋ฅผ ์คํํ์ฌ state ์ถฉ๋ ๋ฐฉ์ง
|
| 222 |
+
- **ํต์ฌ**:
|
| 223 |
+
- ๋
๋ฆฝ๋ ๋จ์ผ ์ง๋ฌธ ๊ทธ๋ํ๋ฅผ ๋ด๋ถ์์ ์คํ
|
| 224 |
+
- outer graph์ scalar state ์ฑ๋ ์ถฉ๋ ๋ฐฉ์ง
|
| 225 |
+
- ๊ฒฐ๊ณผ๋ฅผ `multi_answers` reducer์๋ง ์ถ๊ฐ
|
| 226 |
+
|
| 227 |
+
#### 6. combine_answers_node (์ ๊ท)
|
| 228 |
+
- **์์น**: `src/agent/nodes.py` ๋ผ์ธ 1168
|
| 229 |
+
- **์ญํ **: multi_answers๋ฅผ ํตํฉ Markdown ํฌ๋งท์ผ๋ก ๋ณํ
|
| 230 |
+
- **ํน์ง**: ์๋ fan-in (๋ชจ๋ Send ์๋ฃ ๋๊ธฐ)
|
| 231 |
+
|
| 232 |
+
### ์์ ๋ ๋
ธ๋ (1๊ฐ)
|
| 233 |
+
|
| 234 |
+
#### generate_answer_node (5์ค ์ถ๊ฐ)
|
| 235 |
+
- **์์น**: `src/agent/nodes.py` ๋ผ์ธ 726
|
| 236 |
+
- **์ถ๊ฐ ๋ด์ฉ**:
|
| 237 |
+
```python
|
| 238 |
+
# ๊ธฐ์กด ๋ก์ง ๋ง์ง๋ง์ ์ถ๊ฐ
|
| 239 |
+
if state.is_multi_question:
|
| 240 |
+
updates["multi_answers"] = [{
|
| 241 |
+
"index": state.sub_question_index,
|
| 242 |
+
"question": state.sub_question_text,
|
| 243 |
+
"answer": final_answer
|
| 244 |
+
}]
|
| 245 |
+
```
|
| 246 |
+
|
| 247 |
+
### ๊ทธ๋ํ ์ฌ๊ตฌ์ฑ
|
| 248 |
+
|
| 249 |
+
```python
|
| 250 |
+
# src/agent/graph.py
|
| 251 |
+
|
| 252 |
+
# 1. START ์ง์
์ ๋ณ๊ฒฝ
|
| 253 |
+
graph.add_edge(START, "create_plan") # ๊ธฐ์กด: analyze_question
|
| 254 |
+
|
| 255 |
+
# 2. create_plan ํ ๋ถ๊ธฐ ์ถ๊ฐ
|
| 256 |
+
graph.add_conditional_edges(
|
| 257 |
+
"create_plan",
|
| 258 |
+
route_after_plan,
|
| 259 |
+
{
|
| 260 |
+
"analyze_question": "analyze_question",
|
| 261 |
+
"initiate_dynamic_search": "initiate_dynamic_search",
|
| 262 |
+
"handle_too_many_questions": "handle_too_many_questions"
|
| 263 |
+
}
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
# 3. initiate_dynamic_search ํ fan-out
|
| 267 |
+
graph.add_conditional_edges(
|
| 268 |
+
"initiate_dynamic_search",
|
| 269 |
+
fanout_multi_questions, # List[Send] ๋ฐํ
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
# 4. run_single_question_worker ํ fan-in
|
| 273 |
+
graph.add_edge("run_single_question_worker", "combine_answers")
|
| 274 |
+
|
| 275 |
+
# 5. generate_answer ํ ๋ถ๊ธฐ ์ถ๊ฐ
|
| 276 |
+
graph.add_conditional_edges(
|
| 277 |
+
"generate_answer",
|
| 278 |
+
route_after_generate,
|
| 279 |
+
{
|
| 280 |
+
"combine_answers": "combine_answers",
|
| 281 |
+
END: END
|
| 282 |
+
}
|
| 283 |
+
)
|
| 284 |
+
```
|
| 285 |
+
|
| 286 |
+
## ์ฌ์ฉ ์์
|
| 287 |
+
|
| 288 |
+
### ์์ 1: ๋จ์ผ ์ฃผ์ (๊ธฐ์กด ๋์)
|
| 289 |
+
|
| 290 |
+
```python
|
| 291 |
+
from CodeWeaver.src.agent.graph import create_agent
|
| 292 |
+
from langchain_core.messages import HumanMessage
|
| 293 |
+
|
| 294 |
+
agent = create_agent()
|
| 295 |
+
|
| 296 |
+
result = await agent.ainvoke({
|
| 297 |
+
"user_question": "React hooks ์๋ฒฝ ๊ฐ์ด๋",
|
| 298 |
+
"messages": [HumanMessage(content="React hooks ์๋ฒฝ ๊ฐ์ด๋")]
|
| 299 |
+
})
|
| 300 |
+
|
| 301 |
+
# ๊ฒฐ๊ณผ
|
| 302 |
+
# plan.case: "single_topic"
|
| 303 |
+
# plan.sub_questions: ["hooks๋", "์ฃผ์ hooks", "์ค๋ฌด ํจํด"]
|
| 304 |
+
# ํ๋ฆ: ๊ธฐ์กด ๊ทธ๋ํ 1ํ ์คํ
|
| 305 |
+
# ์ถ๋ ฅ: ์ผ๋ฐ ๋ต๋ณ ํ์
|
| 306 |
+
```
|
| 307 |
+
|
| 308 |
+
### ์์ 2: ๋ค์ค ๋
๋ฆฝ ์ง๋ฌธ (์ ๊ท)
|
| 309 |
+
|
| 310 |
+
```python
|
| 311 |
+
result = await agent.ainvoke({
|
| 312 |
+
"user_question": "JWT๊ฐ ๋ญ์ผ? CORS ์๋ฌ๋ ์ด๋ป๊ฒ ํด๊ฒฐํด?",
|
| 313 |
+
"messages": [HumanMessage(content="JWT๊ฐ ๋ญ์ผ? CORS ์๋ฌ๋ ์ด๋ป๊ฒ ํด๊ฒฐํด?")]
|
| 314 |
+
})
|
| 315 |
+
|
| 316 |
+
# ๊ฒฐ๊ณผ
|
| 317 |
+
# plan.case: "multiple_questions"
|
| 318 |
+
# plan.sub_questions: ["JWT๊ฐ ๋ญ์ผ?", "CORS ์๋ฌ๋ ์ด๋ป๊ฒ ํด๊ฒฐํด?"]
|
| 319 |
+
# ํ๋ฆ: Send API๋ก ๊ทธ๋ํ 2ํ ๋ณ๋ ฌ ์คํ
|
| 320 |
+
# ์ถ๋ ฅ:
|
| 321 |
+
```
|
| 322 |
+
|
| 323 |
+
**์ถ๋ ฅ ์์**:
|
| 324 |
+
```markdown
|
| 325 |
+
# ๋ค์ค ์ง๋ฌธ ๋ต๋ณ
|
| 326 |
+
|
| 327 |
+
์๋ณธ ์ง๋ฌธ: JWT๊ฐ ๋ญ์ผ? CORS ์๋ฌ๋ ์ด๋ป๊ฒ ํด๊ฒฐํด?
|
| 328 |
+
|
| 329 |
+
---
|
| 330 |
+
|
| 331 |
+
## 1. JWT๊ฐ ๋ญ์ผ?
|
| 332 |
+
|
| 333 |
+
JWT(JSON Web Token)๋ ์ธ์ฆ ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ์ ์กํ๊ธฐ ์ํ...
|
| 334 |
+
|
| 335 |
+
[์์ธ ๋ต๋ณ...]
|
| 336 |
+
|
| 337 |
+
---
|
| 338 |
+
|
| 339 |
+
## 2. CORS ์๋ฌ๋ ์ด๋ป๊ฒ ํด๊ฒฐํด?
|
| 340 |
+
|
| 341 |
+
CORS(Cross-Origin Resource Sharing) ์๋ฌ๋...
|
| 342 |
+
|
| 343 |
+
[์์ธ ๋ต๋ณ...]
|
| 344 |
+
```
|
| 345 |
+
|
| 346 |
+
### ์์ 3: ์ง๋ฌธ 3๊ฐ ์ด์
|
| 347 |
+
|
| 348 |
+
```python
|
| 349 |
+
result = await agent.ainvoke({
|
| 350 |
+
"user_question": "JWT? CORS? Docker?",
|
| 351 |
+
"messages": [HumanMessage(content="JWT? CORS? Docker?")]
|
| 352 |
+
})
|
| 353 |
+
|
| 354 |
+
# ๊ฒฐ๊ณผ
|
| 355 |
+
# plan.case: "too_many"
|
| 356 |
+
# ์ถ๋ ฅ:
|
| 357 |
+
```
|
| 358 |
+
|
| 359 |
+
**์ถ๋ ฅ ์์**:
|
| 360 |
+
```
|
| 361 |
+
์ฃ์กํฉ๋๋ค. ํ ๋ฒ์ ์ต๋ 2๊ฐ์ ์ง๋ฌธ๊น์ง๋ง ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
|
| 362 |
+
|
| 363 |
+
๋ค์ ์ค ํ๋๋ฅผ ์ ํํด์ ๋ค์ ์ง๋ฌธํด ์ฃผ์ธ์:
|
| 364 |
+
|
| 365 |
+
1. **ํ๋์ ์ฃผ์ ๋ก ํตํฉํด์ ์ง๋ฌธ**
|
| 366 |
+
์: "JWT ์ธ์ฆ๊ณผ CORS ์ค์ ์ ํจ๊ป ๊ตฌํํ๋ ๋ฐฉ๋ฒ"
|
| 367 |
+
|
| 368 |
+
2. **๊ฐ์ฅ ์ค์ํ 2๊ฐ ์ง๋ฌธ๋ง ์ ํ**
|
| 369 |
+
์: "JWT๊ฐ ๋ญ์ผ? ๋ด ์ฝ๋์ ์ด๋ป๊ฒ ์ ์ฉํด?"
|
| 370 |
+
|
| 371 |
+
3. **์ง๋ฌธ์ ๋๋ ์ ์์ฐจ์ ์ผ๋ก ์ง๋ฌธ**
|
| 372 |
+
์: ๋จผ์ "JWT๊ฐ ๋ญ์ผ?" ์ง๋ฌธ โ ๋ต๋ณ ํ์ธ โ ๋ค์ ์ง๋ฌธ
|
| 373 |
+
|
| 374 |
+
์ด๋ป๊ฒ ๋์๋๋ฆด๊น์?
|
| 375 |
+
```
|
| 376 |
+
|
| 377 |
+
## ํ
์คํธ
|
| 378 |
+
|
| 379 |
+
ํ
์คํธ ํ์ผ์ ํ๋ก์ ํธ ๋ฃจํธ์ ์์ต๋๋ค. (์ญ์ ๋จ - ํ์์ ์ฌ์์ฑ)
|
| 380 |
+
|
| 381 |
+
### ํ
์คํธ ์๋๋ฆฌ์ค
|
| 382 |
+
|
| 383 |
+
1. โ
**๋จ์ผ ์ฃผ์ **: "Spring Security JWT ์ธ์ฆ ๊ตฌํ ๋ฐฉ๋ฒ"
|
| 384 |
+
- ๊ธฐ์กด ๊ทธ๋ํ 1ํ ์คํ
|
| 385 |
+
- multi_answers ๋น์ด์์
|
| 386 |
+
- ์ผ๋ฐ ๋ต๋ณ ํ์
|
| 387 |
+
|
| 388 |
+
2. โ
**๋ค์ค ์ง๋ฌธ 2๊ฐ**: "JWT๊ฐ ๋ญ์ผ? CORS๋?"
|
| 389 |
+
- Send API๋ก ๊ทธ๋ํ 2ํ ๋ณ๋ ฌ ์คํ
|
| 390 |
+
- multi_answers์ 2๊ฐ ํญ๋ชฉ
|
| 391 |
+
- ์น์
๊ตฌ๋ถ๋ ํตํฉ ๋ต๋ณ
|
| 392 |
+
|
| 393 |
+
3. โ
**์ง๋ฌธ 3๊ฐ ์ด์**: "JWT? CORS? Docker?"
|
| 394 |
+
- handle_too_many_questions๋ก ๋ถ๊ธฐ
|
| 395 |
+
- ์น์ ํ ์๋ฌ ๋ฉ์์ง
|
| 396 |
+
- ๋ํ ๊ณ์ ๊ฐ๋ฅ
|
| 397 |
+
|
| 398 |
+
4. โ
**์ฃ์ง ์ผ์ด์ค**: "JWT? CORS? Docker? Redis?"
|
| 399 |
+
- **ํ๋ ๊ฐ๋๋ก ๋ฌด์กฐ๊ฑด too_many ์ฐจ๋จ** (๋ฌผ์ํ 4๊ฐ ๊ฐ์ง)
|
| 400 |
+
- LLM ๋ถ๋ฅ์ ๋ฌด๊ดํ๊ฒ ์ฐจ๋จ ๋ณด์ฅ
|
| 401 |
+
|
| 402 |
+
## ์ฑ๋ฅ ๊ณ ๋ ค์ฌํญ
|
| 403 |
+
|
| 404 |
+
### ๋ณ๋ ฌ ์คํ
|
| 405 |
+
- **๋จ์ผ ์ฃผ์ **: 3๊ฐ ๊ฒ์ ๋
ธ๋ ๋ณ๋ ฌ (๊ธฐ์กด)
|
| 406 |
+
- **๋ค์ค ์ง๋ฌธ (2๊ฐ)**: 2ร3=6๊ฐ ๊ฒ์ ๋
ธ๋ ๋ณ๋ ฌ
|
| 407 |
+
- LangGraph Send API๊ฐ ์๋ ๋ณ๋ ฌํ ๊ด๋ฆฌ
|
| 408 |
+
|
| 409 |
+
### ๋น์ฉ ๊ด๋ฆฌ
|
| 410 |
+
- ์ง๋ฌธ ๊ฐ์ ์ ํ: ์ต๋ 2๊ฐ
|
| 411 |
+
- ๊ฒ์ ๊ฒฐ๊ณผ ๊ฐ์: ์์ค๋น 3-5๊ฐ
|
| 412 |
+
- ๋ค์ค ์ง๋ฌธ ์ ์๋ ๋ถ๋ฅ ์๋ต (๊ธฐ๋ณธ๊ฐ "learning" ์ฌ์ฉ)
|
| 413 |
+
|
| 414 |
+
### ์บ์ฑ
|
| 415 |
+
- **๋จ์ผ ์ฃผ์ **: ์ ์ฒด ๋ต๋ณ ์บ์ โ
|
| 416 |
+
- **๋ค์ค ์ง๋ฌธ**: ๊ฐ ์๋ธ ์ง๋ฌธ ๋ต๋ณ ๊ฐ๋ณ ์บ์ โ
|
| 417 |
+
- Q1 ๋ต๋ณ โ Q1 ์ง๋ฌธ์ผ๋ก ์บ์
|
| 418 |
+
- Q2 ๋ต๋ณ โ Q2 ์ง๋ฌธ์ผ๋ก ์บ์
|
| 419 |
+
- ๋ค์๋ฒ ๋์ผ ์ง๋ฌธ ์ ๊ฐ๋ณ ์บ์ ํํธ ๊ฐ๋ฅ
|
| 420 |
+
|
| 421 |
+
## ๊ธฐ์ ์ ํต์ฌ
|
| 422 |
+
|
| 423 |
+
### 1. Send API ํจํด (Conditional Edge ํจ์ ์ฌ์ฉ)
|
| 424 |
+
|
| 425 |
+
```python
|
| 426 |
+
# โ ์๋ชป๋ ๋ฐฉ๋ฒ: ๋
ธ๋์์ Send ๋ฐํ
|
| 427 |
+
def initiate_dynamic_search_node(state):
|
| 428 |
+
return [Send(...), Send(...)] # ์๋ฌ ๋ฐ์!
|
| 429 |
+
|
| 430 |
+
# โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: conditional edge ํจ์์์ Send ๋ฐํ
|
| 431 |
+
def fanout_multi_questions(state: AgentState) -> List[Send]:
|
| 432 |
+
sends = []
|
| 433 |
+
for i, question in enumerate(sub_questions):
|
| 434 |
+
child_state = state.model_copy(deep=True)
|
| 435 |
+
child_state.user_question = question
|
| 436 |
+
sends.append(Send("run_single_question_worker", child_state))
|
| 437 |
+
return sends
|
| 438 |
+
|
| 439 |
+
# ๊ทธ๋ํ ์ค์
|
| 440 |
+
graph.add_conditional_edges(
|
| 441 |
+
"initiate_dynamic_search",
|
| 442 |
+
fanout_multi_questions, # List[Send] ๋ฐํ
|
| 443 |
+
)
|
| 444 |
+
|
| 445 |
+
# LangGraph๊ฐ ์๋์ผ๋ก:
|
| 446 |
+
# 1. ๋ Send๋ฅผ ๋ณ๋ ฌ ์คํ
|
| 447 |
+
# 2. ๊ฐ Send์ ๋ชจ๋ ๋
ธ๋ ์คํ ๋๊ธฐ
|
| 448 |
+
# 3. ๋ค์ ๊ณตํต ๋
ธ๋๋ก ์ด๋ (combine_answers)
|
| 449 |
+
```
|
| 450 |
+
|
| 451 |
+
### 2. Reducer ์๋ ๋ณํฉ (Reset ๊ธฐ๋ฅ ํฌํจ)
|
| 452 |
+
|
| 453 |
+
```python
|
| 454 |
+
# State ์ ์ (์ปค์คํ
reducer)
|
| 455 |
+
multi_answers: Annotated[List[Dict[str, Any]], merge_multi_answers] = []
|
| 456 |
+
|
| 457 |
+
# merge_multi_answers reducer:
|
| 458 |
+
def merge_multi_answers(old: List[Dict], new: List[Dict]) -> List[Dict]:
|
| 459 |
+
if not new:
|
| 460 |
+
return old
|
| 461 |
+
# Reset ํ ํฐ ์ฒดํฌ
|
| 462 |
+
if new[0].get("__token__") == "__RESET_MULTI_ANS__":
|
| 463 |
+
return new[1:] # ์ด์ ํด ๋์ ๋ฐฉ์ง
|
| 464 |
+
return old + new # ๊ธฐ๋ณธ ๋ณํฉ
|
| 465 |
+
|
| 466 |
+
# create_plan_node์์ ๋งค ์คํ ์์ ์ ๋ฆฌ์
:
|
| 467 |
+
updates["multi_answers"] = [{"__token__": "__RESET_MULTI_ANS__"}]
|
| 468 |
+
|
| 469 |
+
# ๋ณ๋ ฌ ์คํ ์:
|
| 470 |
+
# [Q1_answer] + [Q2_answer] = [Q1_answer, Q2_answer]
|
| 471 |
+
```
|
| 472 |
+
|
| 473 |
+
### 3. Fan-in ๋ณด์ฅ
|
| 474 |
+
|
| 475 |
+
```python
|
| 476 |
+
# ๋ชจ๋ ๊ฒ์ ๋
ธ๋๊ฐ collect_results๋ก ์ฐ๊ฒฐ
|
| 477 |
+
graph.add_edge("search_stackoverflow", "collect_results")
|
| 478 |
+
graph.add_edge("search_github", "collect_results")
|
| 479 |
+
graph.add_edge("search_official_docs", "collect_results")
|
| 480 |
+
|
| 481 |
+
# LangGraph๊ฐ ์๋์ผ๋ก:
|
| 482 |
+
# 1. 3๊ฐ ๊ฒ์ ๋ชจ๋ ์๋ฃ ๋๊ธฐ
|
| 483 |
+
# 2. collect_results 1ํ๋ง ์คํ
|
| 484 |
+
```
|
| 485 |
+
|
| 486 |
+
## ์ฝ๋ ๋ณ๊ฒฝ ์์ฝ
|
| 487 |
+
|
| 488 |
+
### ํ์ผ๋ณ ๋ณ๊ฒฝ์ฌํญ
|
| 489 |
+
|
| 490 |
+
| ํ์ผ | ์ถ๊ฐ | ์์ | ์ญ์ |
|
| 491 |
+
|------|------|------|------|
|
| 492 |
+
| `state.py` | 5 ํ๋, 1 reducer ํจ์ | - | - |
|
| 493 |
+
| `nodes.py` | 5 ๋
ธ๋ + 1 edge ํจ์ (~300์ค) | 2 ๋
ธ๋ (create_plan ํ๋ ๊ฐ๋ ์ถ๊ฐ, generate_answer 5์ค) | - |
|
| 494 |
+
| `graph.py` | 3 routing ํจ์, ์ฃ์ง ์ฌ๊ตฌ์ฑ | build_agent_graph | - |
|
| 495 |
+
|
| 496 |
+
**์ด ๋ณ๊ฒฝ๋**: ~350์ค ์ถ๊ฐ, ~100์ค ์์
|
| 497 |
+
|
| 498 |
+
### ์ฌ์ฌ์ฉ๋ฅ
|
| 499 |
+
|
| 500 |
+
- **๊ธฐ์กด ๋
ธ๋ ์ฌ์ฌ์ฉ**: 12/16 (75%)
|
| 501 |
+
- **๊ธฐ์กด ๋ก์ง ์ฌ์ฌ์ฉ**: ~95% (๊ฒ์, ํ๊ฐ, ํํฐ๋ง, ์์ฝ ๋ฑ)
|
| 502 |
+
- **์๋ก์ด ๊ฐ๋
**: Send API + Reducer๋ง
|
| 503 |
+
|
| 504 |
+
## LangGraph ๊ณต์ ๊ฐ์ด๋๋ผ์ธ ์ค์
|
| 505 |
+
|
| 506 |
+
### โ
Graph API
|
| 507 |
+
- StateGraph ์ฌ์ฉ
|
| 508 |
+
- Pydantic BaseModel state
|
| 509 |
+
- START/END ๋ช
์
|
| 510 |
+
|
| 511 |
+
### โ
Workflows + Agents
|
| 512 |
+
- Send API๋ก ๋์ ๋ณ๋ ฌํ
|
| 513 |
+
- Conditional edges๋ก ๋ผ์ฐํ
|
| 514 |
+
- Fan-out/Fan-in ํจํด
|
| 515 |
+
|
| 516 |
+
### โ
Thinking in LangGraph
|
| 517 |
+
- ๋
ธ๋๋ ์์ ํจ์ (ํ ๊ฐ์ง ์ผ๋ง)
|
| 518 |
+
- State๋ ๋ถ๋ณ ์
๋ฐ์ดํธ
|
| 519 |
+
- Reducer๋ก ๋ณํฉ ์๋ํ
|
| 520 |
+
|
| 521 |
+
## ํ๊ณ ๋ฐ ํฅํ ๊ฐ์
|
| 522 |
+
|
| 523 |
+
### ํ์ฌ ํ๊ณ
|
| 524 |
+
|
| 525 |
+
1. **์ง๋ฌธ ๊ฐ์ ์ ํ**: ์ต๋ 2๊ฐ
|
| 526 |
+
- ๋น์ฉ vs ํ์ง ํธ๋ ์ด๋์คํ
|
| 527 |
+
- ํฅํ 3-4๊ฐ๋ก ํ์ฅ ๊ฐ๋ฅ
|
| 528 |
+
|
| 529 |
+
2. **์บ์ฑ ์ ๋ต**: ํตํฉ ๋ต๋ณ์ ์บ์ ์ ๋จ
|
| 530 |
+
- ๊ฐ ์๋ธ ์ง๋ฌธ์ ๊ฐ๋ณ ์บ์๋จ
|
| 531 |
+
- ๋์ผํ ๋ค์ค ์ง๋ฌธ ์ฌ์
๋ ฅ ์ ๊ฐ๋ณ ์บ์ ํํธ
|
| 532 |
+
|
| 533 |
+
3. **Refinement ๋ฃจํ**: ๋ค์ค ์ง๋ฌธ์์๋ ๊ฐ๊ฐ ๋
๋ฆฝ์ ์ผ๋ก ์๋
|
| 534 |
+
- ํ ์ง๋ฌธ refine ์ ๋ค๋ฅธ ์ง๋ฌธ์ ์ํฅ ์์
|
| 535 |
+
|
| 536 |
+
### ํฅํ ๊ฐ์ ๋ฐฉํฅ
|
| 537 |
+
|
| 538 |
+
1. **๋ ๋ง์ ์ง๋ฌธ ์ง์**: 3-4๊ฐ๊น์ง ํ์ฅ
|
| 539 |
+
2. **ํผํฉ ์ง๋ฌธ ๊ฐ์ง**: "JWT๊ฐ ๋ญ์ผ? ๊ทธ๊ฑธ Spring์ ์ ์ฉํ๋ ค๋ฉด?" (์์ฐจ ์์กด)
|
| 540 |
+
3. **์คํธ๋ฆฌ๋ฐ ๋ต๋ณ**: ๊ฐ ์๋ธ ์ง๋ฌธ ์๋ฃ ์ฆ์ ์คํธ๋ฆฌ๋ฐ
|
| 541 |
+
4. **์ฐ์ ์์**: ์ค์๋์ ๋ฐ๋ผ ์ง๋ฌธ ์์ ์กฐ์
|
| 542 |
+
|
| 543 |
+
## ์ฐธ๊ณ ์๋ฃ
|
| 544 |
+
|
| 545 |
+
- [LangGraph Graph API](https://docs.langchain.com/oss/python/langgraph/graph-api)
|
| 546 |
+
- [LangGraph Workflows + Agents](https://docs.langchain.com/oss/python/langgraph/workflows-agents)
|
| 547 |
+
- [LangGraph Thinking Guide](https://docs.langchain.com/oss/python/langgraph/thinking-in-langgraph)
|
| 548 |
+
- CodeWeaver Phase 3: Open Deep Research
|
| 549 |
+
|
| 550 |
+
## ๋ฌธ์
|
| 551 |
+
|
| 552 |
+
๊ตฌํ ๊ด๋ จ ์ง๋ฌธ์ด๋ ๋ฒ๊ทธ ๋ฆฌํฌํธ๋ ์ด์๋ก ๋ฑ๋กํด์ฃผ์ธ์.
|
| 553 |
+
|
README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: codeweaver-ai
|
| 3 |
+
emoji: ๐ค
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: "4.44.1"
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# CodeWeaver AI (Gradio Space)
|
| 14 |
+
|
| 15 |
+
CodeWeaver๋ฅผ Hugging Face Spaces์์ ์คํํ๊ธฐ ์ํ Gradio ๋ฐ๋ชจ์
๋๋ค.
|
| 16 |
+
|
| 17 |
+
## ์คํ ๋ฐฉ์
|
| 18 |
+
|
| 19 |
+
- Space ์ํธ๋ฆฌ: `app.py` (repo root)
|
| 20 |
+
- ์ค์ Gradio UI: `CodeWeaver/ui/app.py`
|
| 21 |
+
|
| 22 |
+
## ํ์ Secrets (Settings โ Variables and secrets)
|
| 23 |
+
|
| 24 |
+
- `GOOGLE_API_KEY`
|
| 25 |
+
- `TAVILY_API_KEY`
|
| 26 |
+
- `QDRANT_URL`
|
| 27 |
+
- `QDRANT_API_KEY`
|
| 28 |
+
|
| 29 |
+
์ ํ:
|
| 30 |
+
|
| 31 |
+
- `GITHUB_TOKEN`
|
| 32 |
+
- `LANGCHAIN_TRACING_V2`, `LANGCHAIN_API_KEY`, `LANGCHAIN_PROJECT`
|
| 33 |
+
|
| 34 |
+
## ๋ฌธ์
|
| 35 |
+
|
| 36 |
+
- `ARCHITECTURE.md`
|
| 37 |
+
- `DYNAMIC_PARALLEL_SEARCH.md`
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
|
app.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Hugging Face Spaces entrypoint.
|
| 3 |
+
|
| 4 |
+
This file is intentionally minimal:
|
| 5 |
+
- It imports the existing Gradio Blocks app from `CodeWeaver/ui/app.py`
|
| 6 |
+
- It launches it with HF-friendly defaults.
|
| 7 |
+
|
| 8 |
+
Local dev remains unchanged:
|
| 9 |
+
- You can still run `python CodeWeaver/ui/app.py` as before.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import os
|
| 15 |
+
import sys
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def _bootstrap_import_path() -> None:
|
| 20 |
+
# Make `CodeWeaver/` importable as a top-level path so we can `import ui.app`.
|
| 21 |
+
repo_root = Path(__file__).resolve().parent
|
| 22 |
+
codeweaver_root = repo_root / "CodeWeaver"
|
| 23 |
+
sys.path.insert(0, str(codeweaver_root))
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def main() -> None:
|
| 27 |
+
_bootstrap_import_path()
|
| 28 |
+
|
| 29 |
+
# Import AFTER sys.path tweak
|
| 30 |
+
from ui.app import app as demo # type: ignore
|
| 31 |
+
|
| 32 |
+
# HF Spaces commonly provides PORT; fall back to 7860 for local.
|
| 33 |
+
port = int(os.getenv("PORT", "7860"))
|
| 34 |
+
demo.launch(server_name="0.0.0.0", server_port=port, show_api=False)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
if __name__ == "__main__":
|
| 38 |
+
main()
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face Spaces installs dependencies from the repository root.
|
| 2 |
+
# Reuse the project's existing dependency list.
|
| 3 |
+
|
| 4 |
+
-r CodeWeaver/requirements.txt
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
|