Spaces:
Running
Running
| # CodeWeaver ์ํคํ ์ฒ (์ค์ ์ฝ๋ ๊ธฐ์ค) | |
| ์ด ๋ฌธ์๋ ํ์ฌ ์ ์ฅ์์ CodeWeaver๊ฐ **์ด๋ค ์์๋ก ๋์ํ๋์ง**, ๊ทธ๋ฆฌ๊ณ ๊ทธ ์๋ฆฌ๊ฐ ๋ฌด์์ธ์ง(์ํ/๋ผ์ฐํ /๋ณ๋ ฌํ/์บ์)๋ฅผ **์ฝ๋์ 1:1๋ก ์ ํฉ**๋๊ฒ ์ค๋ช ํฉ๋๋ค. | |
| ## ์ ์ฒด ๊ตฌ์ฑ ์์ | |
| - **UI**: Gradio ์ฑํ UI (`CodeWeaver/ui/app.py`) | |
| - ์ฌ์ฉ์ ์ ๋ ฅ์ `AgentState`๋ก ํฌ์ฅํ ๋ค `agent.ainvoke(..., config={"configurable": {"thread_id": ...}})`๋ก ์คํํฉ๋๋ค. | |
| - **์ค์ผ์คํธ๋ ์ด์ (๊ทธ๋ํ)**: LangGraph `StateGraph` (`CodeWeaver/src/agent/graph.py`) | |
| - `START โ create_plan`๋ก ์ง์ ํ, ์ง๋ฌธ ์ ํ/๊ฐ์์ ๋ฐ๋ผ ๋ถ๊ธฐํฉ๋๋ค. | |
| - ์ฒดํฌํฌ์ธํ : `MemorySaver` ์ฌ์ฉ(์ค๋ ๋/์ธ์ ๋จ์ ์ํ ์ ์ง). | |
| - **๋ ธ๋ ๊ตฌํ**: (`CodeWeaver/src/agent/nodes.py`) | |
| - ์ง๋ฌธ ๋ถ์, ์บ์ ์กฐํ, ์๋ ๋ถ๋ฅ, 3์์ค ๋ณ๋ ฌ ๊ฒ์, ๊ฒฐ๊ณผ ํ๊ฐ/๋ฆฌํ์ธ, ํํฐ๋ง/์์ฝ, ๋ต๋ณ ์์ฑ, ๋ค์ค ์ง๋ฌธ ๊ฒฐํฉ ๋ฑ์ ๋ด๋นํฉ๋๋ค. | |
| - **์ํ ๋ชจ๋ธ(Reducer ํฌํจ)**: (`CodeWeaver/src/agent/state.py`) | |
| - `search_results`๋ `Annotated[List[SearchResult], add]`๋ก **๋ณ๋ ฌ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์๋ ๋ณํฉ**๋ฉ๋๋ค. | |
| - `intermediate_steps`, `multi_answers`๋ **๋ฆฌ์ ํ ํฐ์ ์ง์ํ๋ ์ปค์คํ reducer**๋ก, ์ฒดํฌํฌ์ธํ /์ค๋ ๋ ์ ์ง ์ ์ด์ ํด์ ๋์ ์ ๋ฐฉ์งํฉ๋๋ค. | |
| - **์บ์(Vector DB)**: Qdrant Cloud (`CodeWeaver/src/vector_db/qdrant_client.py`) | |
| - ์๋ฒ ๋ฉ์ ๋ก์ปฌ `BAAI/bge-m3`(`sentence-transformers`)๋ก ์์ฑ, Qdrant์ ์ ์ฅ/๊ฒ์ํฉ๋๋ค. | |
| - **๊ฒ์ ์์ค**: (`CodeWeaver/src/tools/search_tools.py`) | |
| - Stack Overflow(๊ณต์ StackExchange API), GitHub Code Search API, Tavily(๊ณต์๋ฌธ์ ๋๋ฉ์ธ ์ ํ) ์ฌ์ฉ. | |
| ## ์ฌ์ฉ์ ์ ๊ณต ๊ทธ๋ํ์์ ์ ํฉ์ฑ | |
| ์ฌ์ฉ์๊ป์ ์ ๊ณตํ Mermaid ๊ทธ๋ํ๋ ์ด ํ๋ก์ ํธ์ ์๋์ **๋๋ถ๋ถ ์ผ์น**ํฉ๋๋ค. | |
| ### ์ผ์นํ๋ ๋ถ๋ถ(ํต์ฌ ํ์ดํ๋ผ์ธ) | |
| - `create_plan`์์ **single_topic / multiple_questions(2๊ฐ) / too_many(3+)** ๋ถ๊ธฐ | |
| - ๋จ์ผ ์ง๋ฌธ(ํน์ ๋จ์ผ ์ฃผ์ )์์: | |
| - `analyze_question โ check_cache โ (hit๋ฉด return_cached_answer) / (miss๋ฉด classify_intent)` | |
| - `classify_intent` ์ดํ 3์์ค ๊ฒ์์ Send API๋ก ๋ณ๋ ฌ ์คํ(fan-out)ํ๊ณ `collect_results`์์ fan-in | |
| - `evaluate_results โ (ํ์ ์ refine_search 1ํ) โ filter_and_score โ summarize_results โ generate_answer` | |
| - `evaluate_results`๊ฐ ๋ถ์กฑํ๋ฉด `refine_search โ classify_intent`๋ก **์ต๋ 1ํ ๋ฃจํ** | |
| ### ์ค์ ์ฝ๋์์ ์ถ๊ฐ/๋ณํ๋ ๋ถ๋ถ(์ค์) | |
| 1) **clarification(๋ณด์ถฉ ์์ฒญ) ์ ์ฉ ๊ฒฝ๋ก๊ฐ ์กด์ฌ** | |
| - `analyze_question` ๊ฒฐ๊ณผ๊ฐ `clarification`์ด๋ฉด | |
| - **์บ์/๊ฒ์์ ์ํํ์ง ์๊ณ ** | |
| - `generate_with_history`๋ก ๋ฐ๋ก ๋ต๋ณํ๊ณ ์ข ๋ฃํฉ๋๋ค. | |
| 2) **multiple_questions fan-out์ `analyze_question`๋ก ์ง์ ๋ค์ด๊ฐ์ง ์์** | |
| ์ฌ์ฉ์ ๊ทธ๋ํ๋ โdynamic์์ Send๋ก analyze_question์ 2๋ฒ ํธ์ถโ ํํ์ ๊ฐ๊น์ง๋ง, ์ค์ ๊ตฌํ์ ๋ค๋ฆ ๋๋ค. | |
| - ์ค์ ๊ตฌํ์ `fanout_multi_questions`๊ฐ `Send("run_single_question_worker", child_state)`๋ฅผ ์์ฑํฉ๋๋ค. | |
| - ์ด์ : outer graph์์ ์ง๋ฌธ 2๊ฐ๋ฅผ ๋์์ ๋์ผ ํ์ดํ๋ผ์ธ(analyze/cache/intent/โฆ)์ผ๋ก ๋๋ฆฌ๋ฉด | |
| - `question_type`, `cached_result` ๊ฐ์ **scalar ์ฑ๋(state ํ๋)**์ด ๋ณ๋ ฌ ์ ๋ฐ์ดํธ ์ถฉ๋์ ์ผ์ผํฌ ์ ์์ต๋๋ค. | |
| - ๋ฐ๋ผ์ **worker ๋ด๋ถ์์ ๋ณ๋์ โ๋จ์ผ ์ง๋ฌธ ๊ทธ๋ํโ๋ฅผ ์คํ**ํ๊ณ , | |
| - outer graph์๋ reducer ์ฑ๋์ธ `multi_answers`๋ง ์ ๋ฐ์ดํธํ์ฌ ์ถฉ๋์ ์ ๊ฑฐํฉ๋๋ค. | |
| ## ์ค์ ์คํ ํ๋ฆ(์ฝ๋ ๊ธฐ์ค) | |
| ### 1) UI โ Agent ์คํ(์ํธ๋ฆฌ) | |
| `CodeWeaver/ui/app.py`์์: | |
| - ์ ๋ ฅ ๋ฌธ์์ด `message`๋ฅผ `AgentState(user_question=..., messages=[HumanMessage(...)], ...)`๋ก ๋ง๋ค๊ณ | |
| - `thread_id`๋ฅผ `config={"configurable":{"thread_id": thread_id}}`๋ก ์ ๋ฌํ์ฌ `agent.ainvoke()` ์คํ | |
| - `MemorySaver`๊ฐ `thread_id` ๋จ์๋ก ์ํ๋ฅผ ๋ณด์กดํฉ๋๋ค. | |
| ### 2) ๋ฉ์ธ ๊ทธ๋ํ(Top-level) ํ๋ฆ | |
| `CodeWeaver/src/agent/graph.py` ๊ธฐ์ค ๋ฉ์ธ ํ๋ฆ์ ์๋์ ๊ฐ์ต๋๋ค. | |
| ```mermaid | |
| graph TD | |
| startNode[START] --> createPlan[create_plan] | |
| createPlan -->|single_topic| analyzeQuestion[analyze_question] | |
| createPlan -->|multiple_questions_2| initiateDynamic[initiate_dynamic_search] | |
| createPlan -->|too_many_3plus| tooMany[handle_too_many_questions] | |
| tooMany --> endNode[END] | |
| analyzeQuestion -->|clarification| withHistory[generate_with_history] | |
| withHistory --> endNode | |
| analyzeQuestion -->|new_topic_or_independent| checkCache[check_cache] | |
| checkCache -->|hit| returnCached[return_cached_answer] | |
| returnCached --> endNode | |
| checkCache -->|miss| classifyIntent[classify_intent] | |
| classifyIntent --> searchSO[search_stackoverflow] | |
| classifyIntent --> searchGH[search_github] | |
| classifyIntent --> searchDocs[search_official_docs] | |
| searchSO --> collect[collect_results] | |
| searchGH --> collect | |
| searchDocs --> collect | |
| collect --> evalNode[evaluate_results] | |
| evalNode -->|needs_refinement_and_lt1| refine[refine_search] | |
| refine --> classifyIntent | |
| evalNode -->|sufficient_or_ge1| searchSubgraph[search_subgraph] | |
| searchSubgraph --> generateAnswer[generate_answer] | |
| generateAnswer --> routeAfterGen[route_after_generate] | |
| routeAfterGen -->|single| endNode | |
| routeAfterGen -->|multi| combine[combine_answers] | |
| combine --> endNode | |
| initiateDynamic --> fanout[fanout_multi_questions] | |
| fanout --> worker[run_single_question_worker] | |
| worker --> combine | |
| ``` | |
| ### 3) `create_plan`: ์ง๋ฌธ ๊ฐ์/ํํ ํ๋ณ + โ3๊ฐ ์ด์โ ํ๋ ๊ฐ๋ | |
| `create_plan_node`๋ ์ ๋ ฅ์ ์๋ 3๊ฐ์ง๋ก ๋ถ๋ฅํฉ๋๋ค. | |
| - **single_topic**: ํ๋์ ์ฃผ์ ๋ฅผ ๋ค์ํ ๊ด์ ์ผ๋ก ๋ฌป๋ ํํ | |
| - **multiple_questions**: ๋ ๋ฆฝ ์ง๋ฌธ 2๊ฐ | |
| - **too_many**: ๋ ๋ฆฝ ์ง๋ฌธ 3๊ฐ ์ด์ | |
| ์ถ๊ฐ๋ก, LLM ๋ถ๋ฅ์ ๋ฌด๊ดํ๊ฒ ๋ค์ ์กฐ๊ฑด์ด๋ฉด **๊ฒฐ์ ๋ก ์ ์ผ๋ก too_many**๋ก ๊ฐ์ ํฉ๋๋ค. | |
| - ๋ฌผ์ํ๊ฐ 3๊ฐ ์ด์ | |
| - ๋๋ โ์ง๋ฌธ ํ๋ณดโ๊ฐ 3๊ฐ ์ด์(์ค๋ฐ๊ฟ/๋ฒํธ/๊ตฌ๋ถ์ ๋ฑ์ผ๋ก ์ถ์ ) | |
| ๋ํ ์ฒดํฌํฌ์ธํ ์ํ ๋์ ์ ๋ง๊ธฐ ์ํด, ๋งค ์คํ ์์ ์ `multi_answers`๋ฅผ ๋ฆฌ์ ํ ํฐ์ผ๋ก ์ด๊ธฐํํฉ๋๋ค. | |
| ### 4) `analyze_question`: ์ง๋ฌธ ํ์ (clarification/new_topic/independent) + ์บ์ ์ ๊ฒฉ์ฑ ํ๋จ | |
| `analyze_question_node`๊ฐ LLM์ผ๋ก ์๋ ๊ฐ์ ์์ฑํฉ๋๋ค. | |
| - `question_type`: `clarification | new_topic | independent` | |
| - `should_cache`: ์บ์ ์ ์ฅ ์ฌ๋ถ | |
| - `canonical_question`: ์บ์์ฉ ์ ๊ทํ ์ง๋ฌธ(should_cache=true์ผ ๋) | |
| ๋ผ์ฐํ ์ `graph.py`์ `route_after_analysis`์์: | |
| - `clarification` โ `generate_with_history` (๊ฒ์/์บ์ ์๋ต) | |
| - ๋๋จธ์ง โ `check_cache` | |
| ### 5) ์บ์(`check_cache` / `return_cached_answer`) | |
| `check_cache_node`๋ Qdrant์์ ์ ์ฌ ์ง๋ฌธ์ ๊ฒ์ํฉ๋๋ค. | |
| - ์๋ฒ ๋ฉ: ๋ก์ปฌ `BAAI/bge-m3` (1024์ฐจ์) | |
| - ์๊ณ๊ฐ: cosine score **0.85 ์ด์**์ด๋ฉด hit๋ก ๊ฐ์ฃผ | |
| hit๋ฉด `return_cached_answer_node`๊ฐ ์ ์ฅ๋ ๋ต๋ณ์ ์ฆ์ ๋ฐํํฉ๋๋ค. | |
| ### 6) ์๋ ๋ถ๋ฅ(`classify_intent`) | |
| `classify_intent_node`๊ฐ ์ง๋ฌธ์ `debugging | learning | code_review`๋ก ๋ถ๋ฅํฉ๋๋ค. | |
| ์ด ๊ฐ์ ๊ฒ์ ๊ฐ์ ๋ฑ ์ผ๋ถ ์ ์ฑ ์ ๋ฐ์๋ฉ๋๋ค(์: StackOverflow๋ debugging์ด๋ฉด ๋ ๋ง์ด ๊ฐ์ ธ์ด). | |
| ### 7) ๋ณ๋ ฌ ๊ฒ์(fan-out) โ ์์ง(fan-in) | |
| `classify_intent` ์ดํ conditional edge ํจ์๊ฐ `Send(...)` 3๊ฐ๋ฅผ ๋ฐํํ์ฌ ๋ณ๋ ฌ๋ก ์คํ๋ฉ๋๋ค. | |
| - `search_stackoverflow_node` | |
| - `search_github_node` | |
| - `search_official_docs_node` | |
| ๊ฐ ๋ ธ๋๋ `{"search_results": [..]}`๋ฅผ ๋ฐํํ๊ณ , `AgentState.search_results`์ reducer(`add`)๊ฐ ์ด๋ฅผ ์๋ ๋ณํฉํฉ๋๋ค. | |
| `collect_results_node`๋ ๋ณํฉ๋ ์ด ๊ฒฐ๊ณผ ๊ฐ์๋ง ์ง๊ณํฉ๋๋ค. | |
| ### 8) ๊ฒฐ๊ณผ ํ๊ฐ(`evaluate_results`)์ ์ฟผ๋ฆฌ ๋ฆฌํ์ธ(`refine_search`) | |
| `evaluate_results_node`๋ ๋ค์ ๊ธฐ์ค์ผ๋ก โ๊ฐ์ ํ์โ๋ฅผ ํ๋จํฉ๋๋ค. | |
| - ๊ฒฐ๊ณผ ๊ฐ์ < 2 โ ๊ฐ์ ํ์ | |
| - (relevance_score๊ฐ ์๋ค๋ฉด) ํ๊ท ์ ์ < 0.5 โ ๊ฐ์ ํ์ | |
| `refine_search_node`๋ LLM์ด `MORE_SPECIFIC | MORE_GENERAL | TRANSLATE` ์ ๋ต์ ์ ํํด ์ฟผ๋ฆฌ๋ฅผ ๊ฐ์ ํฉ๋๋ค. | |
| - ๋ฌดํ ๋ฃจํ ๋ฐฉ์ง: `refinement_count < 1`์ผ ๋๋ง 1ํ ํ์ฉ | |
| - ์ฌ๊ฒ์์ ์ํด `search_results`๋ฅผ ๋น ๋ฆฌ์คํธ๋ก ์ด๊ธฐํํ๊ณ `classify_intent`๋ก ๋๋์๊ฐ๋๋ค. | |
| ### 9) `search_subgraph`: ํํฐ๋ง + ์์ฝ | |
| ๋ฉ์ธ ๊ทธ๋ํ์๋ `search_subgraph`๊ฐ โํ๋์ ๋ ธ๋โ์ฒ๋ผ ๋ถ์ด ์์ต๋๋ค. | |
| - `filter_and_score`: ์ต์ ๊ธธ์ด/URL ์กฐ๊ฑด์ผ๋ก ํํฐ ํ, ์์ ์ผ๋ถ์ ๋ํด ๊ด๋ จ๋ ์ ์ ๋ถ์ฌ | |
| - `summarize_results`: ๊ฐ ๊ฒฐ๊ณผ๋ฅผ 2~3๋ฌธ์ฅ์ผ๋ก ์์ฝ | |
| ### 10) `generate_answer`: ๋ต๋ณ ์์ฑ + (์กฐ๊ฑด๋ถ) ์บ์ ์ ์ฅ | |
| `generate_answer_node`๋ ์๋์ ๋ฐ๋ผ ํ ํ๋ฆฟ์ ๋ฐ๊ฟ ์ต์ข ๋ต๋ณ์ ์์ฑํฉ๋๋ค. | |
| ์บ์ ์ ์ฅ ์ ์ฑ : | |
| - `question_type`๊ฐ `new_topic` ๋๋ `independent`์ด๊ณ `should_cache`๊ฐ true์ด๋ฉด ์ ์ฅ | |
| - `clarification`์ ์ ์ฅํ์ง ์์(๋ผ์ฐํ ์ ๋ณดํต ์ฌ๊ธฐ๋ก ์ค์ง ์์ง๋ง ๋ฐฉ์ด์ ์ผ๋ก ์ฒดํฌ) | |
| ### 11) ๋ค์ค ์ง๋ฌธ(multiple_questions) ์ฒ๋ฆฌ ์๋ฆฌ | |
| ๋ค์ค ์ง๋ฌธ์ ํต์ฌ์ โouter graph๋ ์ถฉ๋ ์์ด orchestration๋ง, ์ค์ ํ์ดํ๋ผ์ธ์ worker ๋ด๋ถ์์ ์คํโ์ ๋๋ค. | |
| #### ํ๋ฆ | |
| - `create_plan(case=multiple_questions)` โ `initiate_dynamic_search` (์ค๋น) | |
| - `fanout_multi_questions`(conditional edge)์ด ์ง๋ฌธ 2๊ฐ๋ฅผ ๊ฐ๊ฐ `run_single_question_worker`๋ก Send | |
| - `run_single_question_worker_node` ๋ด๋ถ์์ **๋จ์ผ ์ง๋ฌธ์ฉ ๊ทธ๋ํ๋ฅผ ๋ณ๋ compile/์คํ** | |
| - worker ๊ฒฐ๊ณผ๋ `multi_answers`์ append(reducer๋ก ๋ณํฉ) | |
| - ๋ชจ๋ worker๊ฐ ๋๋๋ฉด `combine_answers_node`๊ฐ Markdown์ผ๋ก ๊ฒฐํฉ | |
| #### ์ worker๊ฐ ํ์ํ๊ฐ? | |
| outer graph์์ ๋์ผํ state๋ฅผ ๋ณต์ ํด `analyze_question`๋ถํฐ ๋์์ ๋๋ฆฌ๋ฉด, | |
| scalar ์ฑ๋(`question_type`, `cached_result` ๋ฑ)์ด ์๋ก ๋ฎ์ด์ฐ์ผ ์ ์์ต๋๋ค. | |
| ๊ทธ๋์ ์ค์ ๊ตฌํ์: | |
| - worker ๋ด๋ถ์์ ๋จ์ผ ์ง๋ฌธ ๊ทธ๋ํ๋ฅผ ๋๋ฆฌ๊ณ | |
| - outer state์๋ **reducer ์ฑ๋์ธ `multi_answers`๋ง** ์ ๋ฐ์ดํธ | |
| ์ด ๋ฐฉ์์ผ๋ก ๋ณ๋ ฌ ์คํ ์์ ์ฑ์ ํ๋ณดํฉ๋๋ค. | |
| ## ํ๊ฒฝ ๋ณ์(์คํ์ ํ์ํ ์ค์ ๊ฐ) | |
| ํ์: | |
| - `GOOGLE_API_KEY`: Gemini ํธ์ถ(`langchain-google-genai`) | |
| - `QDRANT_URL`, `QDRANT_API_KEY`: Qdrant Cloud ์บ์ | |
| - `TAVILY_API_KEY`: ๊ณต์ ๋ฌธ์ ๊ฒ์(Tavily) | |
| ์ ํ: | |
| - `GITHUB_TOKEN`: GitHub API rate limit ์ํ(์์ผ๋ฉด 60 req/hr ์์ค) | |
| - `LANGCHAIN_TRACING_V2`, `LANGCHAIN_API_KEY`: LangSmith ํธ๋ ์ด์ฑ(์ ํ) | |