Spaces:
Running
A newer version of the Gradio SDK is available:
6.2.0
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-inevaluate_results โ (ํ์ ์ refine_search 1ํ) โ filter_and_score โ summarize_results โ generate_answer
evaluate_results๊ฐ ๋ถ์กฑํ๋ฉดrefine_search โ classify_intent๋ก ์ต๋ 1ํ ๋ฃจํ
์ค์ ์ฝ๋์์ ์ถ๊ฐ/๋ณํ๋ ๋ถ๋ถ(์ค์)
- clarification(๋ณด์ถฉ ์์ฒญ) ์ ์ฉ ๊ฒฝ๋ก๊ฐ ์กด์ฌ
analyze_question๊ฒฐ๊ณผ๊ฐclarification์ด๋ฉด- ์บ์/๊ฒ์์ ์ํํ์ง ์๊ณ
generate_with_history๋ก ๋ฐ๋ก ๋ต๋ณํ๊ณ ์ข ๋ฃํฉ๋๋ค.
- 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๋ง ์ ๋ฐ์ดํธํ์ฌ ์ถฉ๋์ ์ ๊ฑฐํฉ๋๋ค.
- outer graph์๋ reducer ์ฑ๋์ธ
์ค์ ์คํ ํ๋ฆ(์ฝ๋ ๊ธฐ์ค)
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 ๊ธฐ์ค ๋ฉ์ธ ํ๋ฆ์ ์๋์ ๊ฐ์ต๋๋ค.
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 | independentshould_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_nodesearch_github_nodesearch_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๋ก Sendrun_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 ํธ๋ ์ด์ฑ(์ ํ)