κΈμ΅ μμ₯ λΆμμ μν AI μμ΄μ νΈ μμ€ν μΌλ‘, LangGraphλ₯Ό νμ©ν λ©ν° μμ΄μ νΈ μν€ν μ²λ₯Ό ꡬνν©λλ€.
+------------------+ +------------------+ +------------------+
| | | | | |
| Client Request +---->+ FastAPI Server +---->+ SupervisorNode |
| | | | | |
+------------------+ +------------------+ +--------+---------+
|
|
v
+---------------------------+-----+-----+---------------------------+
| | | |
v v v v
+----------+-----------+ +----------+----+ +-+------------+ +---------+-----------+
| | | | | | | |
| NaverNewsSearcherNode| | Future Node 1 | | Future Node 2| | ReportAssistantNode |
| | | | | | | |
+----------------------+ +---------------+ +--------------+ +---------------------+
LangGraph Multi-Agent Architecture
μμ‘΄μ± κ΄λ¦¬λ₯Ό μν΄ κ°λ₯νλ©΄ uv
λ₯Ό μ¬μ©νκΈ°λ₯Ό κΆμ₯ν©λλ€.
using uv(recommended)
- how to install
uv
pip install uv
# or
curl -LsSf https://astral.sh/uv/install.sh | sh
- run market-analysis
git clone https://github.com/FinAgent-Lab/market-analysis-team
cd market-analysis-team
uv sync
uv run main.py
using pip
git clone https://github.com/FinAgent-Lab/market-analysis-team
cd market-analysis-team
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python main.py
μ½λλ₯Ό μΌκ΄μ± μκ² μ μ§νκΈ° μν΄ ruff
λꡬλ₯Ό μ¬μ©ν©λλ€. μ΄ λꡬλ λ§μ μ€νμμ€ νλ‘μ νΈμμ νμ€μ κ°κΉκ² μ¬μ©λκ³ μμ΅λλ€.
μ΄ λꡬλ₯Ό μ¬μ©νκΈ° μν΄μλ pipλ₯Ό μ΄μ©ν΄μ μ€μΉν΄λ λκ³ uv λꡬλ₯Ό μ¬μ©ν΄λ λ©λλ€.
μ¬κΈ°μλ uv λκ΅¬μΈ uvx
λ₯Ό μ¬μ©νλ λ°©λ²μ μ€λͺ
ν©λλ€.
$ uvx ruff check --fix
$ uvx ruff format
μμ΄μ νΈ κ°λ°νλ©° μμ΄μ νΈμ λμμ±μ μμνκ² νμΈνκ³ μ νμμ΅λλ€. μ΄λ₯Ό μν΄ λ³Έ νλ‘μ νΈλ UI λλ APIλ₯Ό νμ©ν©λλ€.
ꡬν체λ μΆμ ν΄λμ€ Nodeλ₯Ό μ€μ²΄ννμ¬ μμ±ν©λλ€.
μΆμ ν΄λμ€ Nodeμ μ£Όμ ν¨μλ __call__
, _run
, _invoke
, invoke
μ΄λ©°, κ°λ° μμλ _run
ν¨μμ _invoke
ν¨μ(μ νμ¬ν)λ₯Ό μμ±ν©λλ€.
_run
ν¨μλ SupervisorNode
μ μ°κ²°μ μν ν¨μμ
λλ€.
_invoke
ν¨μλ API μλ²λ₯Ό ν΅ν΄ λ³λμ μΈν°νμ΄μ€λ₯Ό κ°κ³ μΆλ€λ©΄ ꡬνν©λλ€.
ν΄λμ€ κ΄κ³λλ λ€μκ³Ό κ°μ΅λλ€.
src/graph/nodes/
κ²½λ‘μ μμ΄μ νΈ μ½λλ₯Ό μμ±ν©λλ€.- μ½λλ λ€μκ³Ό κ°μ΄ μμ±ν©λλ€.
class NaverNewsSearcherNode(Node):
def __init__(self):
super().__init__()
# μμ΄μ νΈμκ² μν μ λΆμ¬νκΈ° μν ν둬ννΈμ
λλ€.
# κ²½μ°μ λ°λΌμλ νμμμ μ μμ΅λλ€.
self.system_prompt = (
"You are a news search agent for korean news using naver search api."
"Only use korean source and data to conduct news search."
"Do nothing else"
)
self.agent = None
self.tools = [NaverNewsSearch(sort="date")]
# Supervisor λ
Έλμμ νΈμΆνμ μμ λ‘μ§μ ꡬννλ ν¨μ. μ€μ λ‘λ base nodeμ runν¨μμμ μ΄ ν¨μλ₯Ό νΈμΆν©λλ€.
def _run(self, state: dict) -> dict:
# region [μμ΄μ νΈ λμ λ‘μ§ κ΅¬ν]
# -----------------------------------------------------------------------
if self.agent is None:
assert state["llm"] is not None, "The State model should include llm"
llm = state["llm"]
self.agent = create_react_agent(
llm,
self.tools,
prompt=self.system_prompt,
)
result = self.agent.invoke(state)
# ----------------------------------------------------------------------
# endregion [μμ΄μ νΈ λμ λ‘μ§ κ΅¬ν λ]
self.logger.info(f" result: \n{result['messages'][-1].content}")
# μ΄ λΆλΆμ λμΌν κ²μΌλ‘ μμν©λλ€.
return Command(
update={
"messages": [
HumanMessage(
content=result["messages"][-1].content,
name="naver_news_searcher",
)
]
},
goto="supervisor",
)
# _run ν¨μμ μ μ¬νλ, apiλ₯Ό ν΅ν΄ λμμ νμΈνκΈ° μν ν¨μ. supervisorμλ μκ΄ μμ΅λλ€.
# μ΄ λΆλΆμ base nodeμ invokeν¨μμμ νΈμΆν©λλ€.
# api μλ²μ λ
ΈμΆνκΈ° μν΄ λ³λμ ν¨μλ‘ λΆλ¦¬νμμΌλ©°, μλν¬μΈνΈ κ²½λ‘λ μλ¬Έμ λ° Nodeλ₯Ό μ κ±°ν ννμ
λλ€.
# - μμ : SampleNode ν΄λμ€λ‘ μ½λλ₯Ό μμ± μ, μλν¬μΈνΈλ /api/sample μ
λλ€.
# OpenWebUIμ μ°λνκΈ° μνλ€λ©΄ RawResponse λͺ¨λΈλ‘ 리ν΄ν μ μλλ‘ μμ±ν©λλ€.
def _invoke(self, query: str) -> RawResponse:
agent = self.agent or create_react_agent(
ChatOpenAI(model=self.DEFAULT_LLM_MODEL),
self.tools,
prompt=self.system_prompt,
)
result = agent.invoke({"messages": [("human", query)]})
return RawResponse(answer=result["messages"][-1].content)
main.py
νμΌμμ λΉλλ₯Ό μ΄μ©νμ¬ λ€μκ³Ό κ°μ΄ λ
Έλλ₯Ό μΆκ°ν μ μμ΅λλ€:
graph_builder.add_node(NaverNewsSearcherNode())
API μλ²μ κΈ°λ³Έ ν¬νΈλ 8000
λ²μ΄λ©°, μλ² νΈμ€νΈμ /docs
κ²½λ‘λ₯Ό ν΅ν΄ λͺ
μΈλ₯Ό νμΈν μ μμ΅λλ€.
- μ:
http://localhost:8000/docs
OpenWebUIμ μΆκ°νκΈ° μν΄μλ pipeline μ½λ μμ±μ΄ νμν©λλ€.
μμ μ½λλ pipelines/agent_pipeline_example.py
λ₯Ό μ°Έκ³ ν©λλ€.
pipeline μ½λλ₯Ό μμ±ν ν, λ©μΈ λΈλμΉμ λ³ν©νλ©΄ λ°°ν¬ κ³Όμ μ λμ μΌλ‘ ν΅ν©μ΄ μ΄λ£¨μ΄μ§λλ€.
...
class Pipeline:
class Valves(BaseModel):
pass
def __init__(self):
# self.name μ λͺ¨λΈ μ΄λ¦ μ€μ
self.name = "Market Analysis - Naver News Searcher"
# self.agent_nameμ api μλ²μ ν΄λΉ μμ΄μ νΈ μλν¬μΈνΈ μ΄λ¦μΌλ‘ μμ±
self.agent_name = "navernewssearcher"
...
OpenWebUIμ μ μν΄λ³΄λ©΄ λ€μκ³Ό κ°μ΄ μΆκ°λμ΄μμμ νμΈν μ μμ΅λλ€.
/
βββ .env # κ°μΈλ³ API Keyλ± νκ²½λ³μ μ€μ
βββ .env.tamplate # νκ²½λ³μ ν
νλ¦Ώ
βββ requirements.txt # νμ λΌμ΄λΈλ¬λ¦¬
βββ pyproject.toml # νλ‘μ νΈ μ€μ
βββ main.py # μ 체 μν¬νλ‘μ° μ μ λ° μλΉμ€ μμμ
βββ startup.py # μμ‘΄μ± μ£Όμ
컨ν
μ΄λ μ€μ
βββ api/ # API μλ² κ΄λ ¨ λͺ¨λ
β βββ server.py # FastAPI μλ² κ΅¬ν
βββ src/ # μμ€ μ½λ
β βββ graph/ # LangGraph κ΄λ ¨ λͺ¨λ
β β βββ builder.py # κ·Έλν λΉλ ν΄λμ€
β β βββ nodes/ # κ·Έλν λ
Έλ λͺ¨λ
β β βββ base.py # κΈ°λ³Έ λ
Έλ ν΄λμ€
β β βββ supervisor.py # μνΌλ°μ΄μ λ
Έλ
β β βββ naver_news_searcher.py # λ€μ΄λ² λ΄μ€ κ²μ λ
Έλ
β β βββ ... # κΈ°ν μΆκ° λ
Έλ
β β βββ report_assistant.py # λ³΄κ³ μ μμ± λ
Έλ
β βββ models/ # λ°μ΄ν° λͺ¨λΈ
β β βββ graph_state.py # κ·Έλν μν λͺ¨λΈ
β βββ tools/ # λꡬ λͺ¨λ
β βββ utils/ # μ νΈλ¦¬ν° ν¨μ
β βββ logger.py # λ‘κΉ
μ€μ
βββ images/ # λ컀 μ΄λ―Έμ§ κ΄λ ¨ νμΌ
β βββ backend/
β βββ Dockerfile # λ°±μλ λ컀 νμΌ
βββ tests/ # ν
μ€νΈ μ½λ
This project is licensed under the MIT License.