Language Agent Tree Search
Zhou 등이 제안한 Language Agent Tree Search (LATS)는 반영/평가와 검색(특히 몬테카를로 트리 검색)을 결합하여 ReACT, Reflexion 또는 Tree of Thoughts와 같은 유사한 기법보다 전반적으로 더 나은 작업 성능을 달성하는 범용 LLM 에이전트 검색 알고리즘입니다.

주요 4단계로 구성됩니다:
- Select (선택): 단계 (2)의 누적 보상을 기반으로 최선의 다음 행동을 선택합니다. 솔루션을 찾았거나 최대 검색 깊이에 도달하면 응답하고, 그렇지 않으면 검색을 계속합니다.
- Expand and simulate (확장 및 시뮬레이션): 취할 수 있는 “최선의” 5가지 잠재적 행동을 선택하고 병렬로 실행합니다.
- Reflect + Evaluate (반영 + 평가): 이러한 행동의 결과를 관찰하고 반영(그리고 가능하면 외부 피드백)을 기반으로 결정을 점수화합니다.
- Backpropagate (역전파): 결과를 기반으로 루트 궤적의 점수를 업데이트합니다.
설정
도구로 tavily search를 사용합니다. API 키는 여기에서 얻거나 다른 도구로 대체할 수 있습니다.
import getpass
import os
from dotenv import load_dotenv
load_dotenv("../../.env", override=True)
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_PROJECT"] = "langchain-academy"
def _set_env(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
_set_env("OPENAI_API_KEY")
_set_env("TAVILY_API_KEY")그래프 상태
LATS는 (탐욕적) 몬테카를로 트리 검색을 기반으로 합니다. 각 검색 단계마다 “상한 신뢰 구간(upper confidence bound)“이 가장 높은 노드를 선택합니다. 이는 활용(가장 높은 평균 보상)과 탐색(가장 적은 방문)의 균형을 맞추는 메트릭입니다. 해당 노드에서 시작하여 N개(이 경우 5개)의 새로운 후보 행동을 생성하고 트리에 추가합니다. 유효한 솔루션을 생성했거나 최대 롤아웃 수(검색 트리 깊이)에 도달하면 검색을 중지합니다.

LangGraph 상태는 두 가지 항목으로 구성됩니다:
- 검색 트리의 루트
- 사용자 입력
from collections import deque
import math
from typing import Optional
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
from pydantic import BaseModel, Field
class Reflection(BaseModel):
reflections: str = Field(
description="응답의 충분성, 과잉성, 그리고 전반적인 품질에 대한 비평과 성찰"
)
score: int = Field(
description="후보 응답의 품질에 대한 0-10점 점수.",
gte=0,
lte=10,
)
found_solution: bool = Field(
description="응답이 질문이나 작업을 완전히 해결했는지 여부."
)
def as_message(self):
return HumanMessage(content=f"추론: {self.reflections}\n점수: {self.score}")
@property
def normalized_score(self) -> float:
return self.score / 10.0
class Node:
def __init__(
self,
messages: list[BaseMessage],
reflection: Reflection,
parent: Optional["Node"] = None,
):
self.messages = messages
self.parent = parent
self.children = []
self.value = 0
self.visits = 0
self.reflection = reflection
self.depth = parent.depth + 1 if parent is not None else 1
self._is_solved = reflection.found_solution if reflection else False
if self._is_solved:
self._mark_tree_as_solved()
self.backpropagate(reflection.normalized_score)
def __repr__(self) -> str:
return (
f"<Node value={self.value}, visits={self.visits},"
f" solution={self.messages} reflection={self.reflection}/>"
)
@property
def is_solved(self):
"""해결책이 존재하면 검색을 종료할 수 있습니다."""
return self._is_solved
@property
def is_terminal(self):
return not self.children
@property
def best_child_score(self):
"""가장 높은 가치를 가진 자식을 반환합니다."""
if not self.children:
return None
return max(self.children, key=lambda child: int(child.is_solved) * child.value)
@property
def height(self) -> int:
"""트리를 얼마나 멀리 펼쳤는지 확인합니다."""
if self.children:
return 1 + max([child.height for child in self.children])
return 1
def upper_confidence_bound(self, exploration_weight=1.0):
"""UCT 점수를 반환합니다. 이는 브랜치의 탐험과 활용의 균형을 맞추는 데 도움이 됩니다."""
if self.parent is None:
raise ValueError("루트 노드에서 UCT를 얻을 수 없습니다")
if self.visits == 0:
return self.value
# 높은 가치의 궤적 활용을 장려합니다
average_reward = self.value / self.visits
# 덜 방문한 궤적의 탐험을 장려합니다
exploration_term = math.sqrt(math.log(self.parent.visits) / self.visits)
return average_reward + exploration_weight * exploration_term
def backpropagate(self, reward: float):
"""이 노드와 부모들의 점수를 업데이트합니다."""
node = self
while node:
node.visits += 1
node.value = (node.value * (node.visits - 1) + reward) / node.visits
node = node.parent
def get_messages(self, include_reflections: bool = True):
if include_reflections:
return self.messages + [self.reflection.as_message()]
return self.messages
def get_trajectory(self, include_reflections: bool = True) -> list[BaseMessage]:
"""이 검색 브랜치를 나타내는 메시지를 가져옵니다."""
messages = []
node = self
while node:
messages.extend(
node.get_messages(include_reflections=include_reflections)[::-1]
)
node = node.parent
# 최종 역추적된 궤적을 뒤집어서 올바른 순서로 반환합니다
return messages[::-1] # 루트 솔루션, 성찰, 자식 1, ...
def _get_all_children(self):
all_nodes = []
nodes = deque()
nodes.append(self)
while nodes:
node = nodes.popleft()
all_nodes.extend(node.children)
for n in node.children:
nodes.append(n)
return all_nodes
def get_best_solution(self):
"""현재 서브트리 내에서 최상의 솔루션을 반환합니다."""
all_nodes = [self] + self._get_all_children()
best_node = max(
all_nodes,
# 모든 비터미널, 비솔루션 궤적을 필터링합니다
key=lambda node: int(node.is_terminal and node.is_solved) * node.value,
)
return best_node
def _mark_tree_as_solved(self):
parent = self.parent
while parent:
parent._is_solved = True
parent = parent.parent그래프 상태 자체
주요 구성 요소는 루트 노드로 표현되는 트리입니다.
from typing import TypedDict
class TreeState(TypedDict):
# 전체 트리
root: Node
# 원본 입력
input: strLanguage Agent 정의
에이전트는 세 가지 주요 LLM 기반 프로세스를 갖습니다:
- Reflect (반영): 도구 응답을 기반으로 행동을 점수화합니다.
- Initial response (초기 응답): 루트 노드를 생성하고 검색을 시작합니다.
- Expand (확장): 현재 트리에서 가장 좋은 지점으로부터 5개의 후보 “다음 단계”를 생성합니다.
더 “근거 있는” 도구 애플리케이션(예: 코드 합성)의 경우, 반영/보상 단계에 코드 실행을 통합할 수 있습니다. 이러한 유형의 외부 피드백은 매우 유용합니다(이미 복잡한 예제 노트북에 복잡성을 추가하지만).
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4.1-mini")도구
예제로, language agent에게 검색 엔진을 제공합니다.
from langchain_tavily import TavilySearch
from langgraph.prebuilt import ToolNode
tavily_tool = TavilySearch(max_results=5)
tools = [tavily_tool]
tool_node = ToolNode(tools=tools)반성
반성 체인은 결정과 도구 응답을 기반으로 에이전트 출력을 점수화합니다. 다른 두 노드 내에서 이를 호출합니다.
from langchain_core.output_parsers.openai_tools import (
PydanticToolsParser,
)
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import chain as as_runnable
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"아래 사용자 질문에 대한 어시스턴트 응답을 성찰하고 평가하세요.",
),
("user", "{input}"),
MessagesPlaceholder(variable_name="candidate"),
]
)
reflection_llm_chain = (
prompt
| llm.bind_tools(tools=[Reflection], tool_choice="Reflection").with_config(
run_name="Reflection"
)
| PydanticToolsParser(tools=[Reflection])
)
@as_runnable
def reflection_chain(inputs) -> Reflection:
tool_choices = reflection_llm_chain.invoke(inputs)
reflection = tool_choices[0]
if not isinstance(inputs["candidate"][-1], AIMessage):
reflection.found_solution = False
return reflection초기 응답
첫 번째 단계에서 생성된 단일 루트 노드로 시작합니다. 사용자 입력에 대해 도구 호출 또는 응답으로 답변합니다.
from langchain_core.output_parsers.openai_tools import JsonOutputToolsParser
prompt_template = ChatPromptTemplate.from_messages(
[
(
"system",
"당신은 AI 어시스턴트입니다.",
),
("user", "{input}"),
MessagesPlaceholder(variable_name="messages", optional=True),
]
)
initial_answer_chain = prompt_template | llm.bind_tools(tools=tools).with_config(
run_name="GenerateInitialCandidate"
)
parser = JsonOutputToolsParser(return_id=True)initial_response = initial_answer_chain.invoke(
{"input": "리튬 오염에 관한 연구 보고서를 작성하세요."}
)
initial_response.pretty_print()==================================[1m Ai Message [0m==================================
리튬 오염에 관한 연구 보고서의 주요 내용을 아래와 같이 구성할 수 있습니다. 필요하신 구체적인 항목이나 깊이, 형식(예: 학술 논문, 간단 보고서 등)이 있다면 알려주세요.
---
# 리튬 오염 연구 보고서
## 1. 서론
- 리튬의 중요성: 배터리, 전자기기, 전기차 등에서의 활용 확대.
- 리튬 채굴과 생산 증가에 따른 환경 문제 대두.
- 연구 목적: 리튬 오염의 원인, 현황, 영향 및 대응 방안 분석.
## 2. 리튬 오염의 원인
- 리튬 채굴 과정에서의 토양과 수질 오염.
- 리튬 배터리 제조 및 폐기 과정에서 발생하는 유해 물질.
- 불법 폐기 및 재활용 미흡으로 인한 환경 노출.
## 3. 리튬 오염 현황
- 주요 리튬 채굴 지역(예: 칠레, 호주, 중국)의 환경 오염 사례.
- 폐배터리 처리 시설 주변의 리튬 농도 증가.
- 수질과 토양 내 리튬 농도의 국제적/국내적 모니터링 현황.
## 4. 리튬 오염의 환경 및 건강 영향
- 토양과 지하수 오염에 따른 생태계 교란.
- 인체 건강에 미치는 영향: 신경계, 내분비계 장애 가능성.
- 농작물 및 식수 오염 문제.
## 5. 리튬 오염 대응 및 관리 방안
- 리튬 채굴 및 생산 시 환경 친화적 공정 도입.
- 배터리 수명 연장 및 재활용 기술 개발과 적용 확대.
- 정부 및 국제기구의 규제 강화 및 환경 기준 마련.
- 지역사회 참여 및 지속적 환경 모니터링 강화.
## 6. 결론
- 리튬은 필수 자원이지만 환경 오염 문제를 유발할 수 있음.
- 지속 가능한 리튬 산업 발전을 위해서는 체계적인 관리와 기술 혁신 필요.
- 다각적 연구 및 정책 협력을 통한 리튬 오염 최소화 방향 제시.
## 참고문헌
- 관련 학술 논문, 보고서, 국제기구 발표 자료 등
---
이 보고서 초안을 토대로 구체적인 내용이나 최신 데이터가 필요하면 말씀해 주세요. 추가로 최신 연구 결과나 사례를 찾아 드릴 수도 있습니다.
시작 노드
후보 생성과 반영을 그래프의 단일 노드로 패키징합니다. 다음 함수로 표현됩니다:
# 그래프에 추가할 노드를 정의합니다
def generate_initial_response(state: TreeState) -> dict:
"""초기 후보 응답을 생성합니다."""
res = initial_answer_chain.invoke({"input": state["input"]})
parsed = parser.invoke(res)
tool_responses = [
tool_node.invoke(
{
"messages": [
AIMessage(
content="",
tool_calls=[
{"name": r["type"], "args": r["args"], "id": r["id"]}
],
)
]
}
)
for r in parsed
]
output_messages = [res] + [tr["messages"][0] for tr in tool_responses]
reflection = reflection_chain.invoke(
{"input": state["input"], "candidate": output_messages}
)
root = Node(output_messages, reflection=reflection)
return {
**state,
"root": root,
}후보 생성
다음 코드는 동일한 LLM에게 확인할 N개의 추가 후보를 생성하도록 프롬프트합니다.
from langchain_core.prompt_values import ChatPromptValue
from langchain_core.runnables import RunnableConfig
# 이것은 환경에서 행동을 샘플링하기 위해
# 단일 입력에 대해 N개의 후보 값을 생성합니다
def generate_candidates(messages: ChatPromptValue, config: RunnableConfig):
n = config["configurable"].get("N", 5)
bound_kwargs = llm.bind_tools(tools=tools).kwargs
chat_result = llm.generate(
[messages.to_messages()],
n=n,
callbacks=config["callbacks"],
run_name="GenerateCandidates",
**bound_kwargs,
)
return [gen.message for gen in chat_result.generations[0]]
expansion_chain = prompt_template | generate_candidatesres = expansion_chain.invoke({"input": "리튬 오염에 관한 연구 보고서를 작성하세요."})
res[AIMessage(content='리튬 오염에 관한 연구 보고서 작성을 위해 다음과 같은 구성을 제안합니다.\n\n1. 서론\n- 리튬의 개요 및 중요성\n- 리튬 사용 증가와 그에 따른 환경적 문제\n\n2. 리튬 오염의 원인\n- 산업적 배출 (배터리 제조 및 폐기)\n- 채굴 과정에서의 환경 영향\n- 기타 원인 (폐기물 처리 등)\n\n3. 리튬 오염의 영향\n- 수질 오염과 생태계 피해\n- 토양 오염 및 농업에 미치는 영향\n- 인간 건강에 미치는 잠재적 영향\n\n4. 리튬 오염 현황\n- 주요 오염 지역 사례 연구\n- 최근 연구 동향 및 통계\n\n5. 리튬 오염 저감 방안\n- 친환경 기술 개발 및 적용\n- 정책 및 규제 강화\n- 폐기물 관리 및 재활용\n\n6. 결론\n- 연구 요약 및 제언\n\n이 보고서의 각 섹션에 대해 자세한 내용을 수집하고 작성할 수 있도록 정보를 조사해 드릴까요? 어떤 부분부터 시작하면 좋을지 알려 주세요.', additional_kwargs={'refusal': None}, response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='run--fc919ad1-20b3-42cd-9933-ef5be075a0ee-0', usage_metadata={'input_tokens': 1292, 'output_tokens': 599, 'total_tokens': 1891, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),
AIMessage(content='리튬 오염에 관한 연구 보고서 작성에 앞서, 최신 정보와 신뢰할 만한 근거 자료를 조사하여 충실한 내용을 담고자 합니다. 관련된 최신 연구 결과, 환경 영향, 오염원, 그리고 대처 방안 등에 대해 조사하겠습니다. 잠시만 기다려 주세요.', additional_kwargs={'tool_calls': [{'id': 'call_xx5Z5O6x1qhwqXlO5WvERqSu', 'function': {'arguments': '{"query":"리튬 오염 연구","search_depth":"advanced"}', 'name': 'tavily_search'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'logprobs': None}, id='run--fc919ad1-20b3-42cd-9933-ef5be075a0ee-1', tool_calls=[{'name': 'tavily_search', 'args': {'query': '리튬 오염 연구', 'search_depth': 'advanced'}, 'id': 'call_xx5Z5O6x1qhwqXlO5WvERqSu', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1292, 'output_tokens': 599, 'total_tokens': 1891, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),
AIMessage(content='리튬 오염에 관한 연구 보고서 작성에 앞서, 최신 학술 자료 및 환경 관련 보고서를 참조하여 정확하고 신뢰할 수 있는 정보를 제공하고자 합니다. 잠시만 기다려 주세요.', additional_kwargs={'tool_calls': [{'id': 'call_PbhCCmHgQRz5B8X3Bh30MzgZ', 'function': {'arguments': '{"query":"리튬 오염 연구 보고서","search_depth":"advanced","include_images":false,"time_range":"year","topic":"general"}', 'name': 'tavily_search'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'logprobs': None}, id='run--fc919ad1-20b3-42cd-9933-ef5be075a0ee-2', tool_calls=[{'name': 'tavily_search', 'args': {'query': '리튬 오염 연구 보고서', 'search_depth': 'advanced', 'include_images': False, 'time_range': 'year', 'topic': 'general'}, 'id': 'call_PbhCCmHgQRz5B8X3Bh30MzgZ', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1292, 'output_tokens': 599, 'total_tokens': 1891, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),
AIMessage(content='리튬 오염에 관한 연구 보고서를 작성하기 위해 최근 연구 동향, 리튬 오염의 원인, 영향, 그리고 대처 방안에 대한 정보를 수집하겠습니다. 최신 자료 조사를 진행하겠습니다.', additional_kwargs={'tool_calls': [{'id': 'call_JOzoKJyhQDJnBS49L2JdRYUo', 'function': {'arguments': '{"query":"리튬 오염 연구 최신 동향 원인 영향 대처 방안","search_depth":"advanced","time_range":"year","topic":"general"}', 'name': 'tavily_search'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'logprobs': None}, id='run--fc919ad1-20b3-42cd-9933-ef5be075a0ee-3', tool_calls=[{'name': 'tavily_search', 'args': {'query': '리튬 오염 연구 최신 동향 원인 영향 대처 방안', 'search_depth': 'advanced', 'time_range': 'year', 'topic': 'general'}, 'id': 'call_JOzoKJyhQDJnBS49L2JdRYUo', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1292, 'output_tokens': 599, 'total_tokens': 1891, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),
AIMessage(content='리튬 오염에 관한 연구 보고서를 작성하기 위해 주요 내용을 조사하고 정리하겠습니다. 잠시만 기다려 주세요.', additional_kwargs={'tool_calls': [{'id': 'call_RuM1pLbLTHxo5kmXGkQFnFTi', 'function': {'arguments': '{"query":"리튬 오염 연구 논문 최신","search_depth":"advanced"}', 'name': 'tavily_search'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'logprobs': None}, id='run--fc919ad1-20b3-42cd-9933-ef5be075a0ee-4', tool_calls=[{'name': 'tavily_search', 'args': {'query': '리튬 오염 연구 논문 최신', 'search_depth': 'advanced'}, 'id': 'call_RuM1pLbLTHxo5kmXGkQFnFTi', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1292, 'output_tokens': 599, 'total_tokens': 1891, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]
후보 생성 노드
후보 생성과 반영 단계를 다음 “expand” 노드로 패키징합니다. 실행 속도를 높이기 위해 모든 작업을 배치 프로세스로 수행합니다.
from collections import defaultdict
def select(root: Node) -> dict:
"""루트 노드에서 시작하여 리프 노드에 도달할 때까지 각 트리 레벨에서 자식 노드가 선택됩니다."""
if not root.children:
return root
node = root
while node.children:
max_child = max(node.children, key=lambda child: child.upper_confidence_bound())
node = max_child
return node
def expand(state: TreeState, config: RunnableConfig) -> dict:
"""트리에서 "최고의" 노드부터 시작하여 다음 단계를 위한 N개의 후보를 생성합니다."""
root = state["root"]
best_candidate: Node = select(root)
messages = best_candidate.get_trajectory()
# 단일 자식 후보로부터 N개의 후보를 생성합니다
new_candidates = expansion_chain.invoke(
{"input": state["input"], "messages": messages}, config
)
parsed = parser.batch(new_candidates)
flattened = [
(i, tool_call)
for i, tool_calls in enumerate(parsed)
for tool_call in tool_calls
]
tool_responses = [
(
i,
tool_node.invoke(
{
"messages": [
AIMessage(
content="",
tool_calls=[
{
"name": tool_call["type"],
"args": tool_call["args"],
"id": tool_call["id"],
}
],
)
]
}
),
)
for i, tool_call in flattened
]
collected_responses = defaultdict(list)
for i, resp in tool_responses:
collected_responses[i].append(resp["messages"][0])
output_messages = []
for i, candidate in enumerate(new_candidates):
output_messages.append([candidate] + collected_responses[i])
# 각 후보에 대해 성찰합니다
# 외부 검증이 있는 작업의 경우 여기에 추가합니다.
reflections = reflection_chain.batch(
[{"input": state["input"], "candidate": msges} for msges in output_messages],
config,
)
# 트리 확장
child_nodes = [
Node(cand, parent=best_candidate, reflection=reflection)
for cand, reflection in zip(output_messages, reflections)
]
best_candidate.children.extend(child_nodes)
# 트리를 이미 직접 확장했으므로 상태만 반환합니다
return state그래프 생성
두 노드가 정의되었으므로 그래프를 정의할 준비가 되었습니다. 각 에이전트 단계 후에 종료할 수 있는 옵션이 있습니다.
from langgraph.graph import END, START, StateGraph
def should_loop(state: TreeState):
"""트리 검색을 계속할지 결정합니다."""
root = state["root"]
if root.is_solved:
return END
if root.height > 5:
return END
return "expand"
builder = StateGraph(TreeState)
builder.add_node("start", generate_initial_response)
builder.add_node("expand", expand)
builder.add_edge(START, "start")
builder.add_conditional_edges(
"start",
# 확장/롤아웃하거나 종료합니다
should_loop,
["expand", END],
)
builder.add_conditional_edges(
"expand",
# 롤아웃을 계속하거나 종료합니다
should_loop,
["expand", END],
)
graph = builder.compile()graph
호출
question = "상위 5개 가장 흔한 새들 각각에 대한 평균 크기와 무게, 그리고 가장 오래된 기록된 사례가 포함된 표를 생성하세요."
last_step = None
for step in graph.stream({"input": question}):
last_step = step
step_name, step_state = next(iter(step.items()))
print(step_name)
print("rolled out: ", step_state["root"].height)
print("---")start
rolled out: 1
---
expand
rolled out: 2
---
solution_node = last_step["expand"]["root"].get_best_solution()
best_trajectory = solution_node.get_trajectory(include_reflections=False)
print(best_trajectory[-1].content)가장 흔하게 알려진 대표적인 5종의 새들에 대해 평균 크기, 평균 무게, 그리고 가장 오래된 기록된 사례를 정리한 표를 아래에 작성했습니다. 일반적으로 흔한 새들은 지역과 환경에 따라 다르지만, 전 세계적으로 흔한 새들을 중심으로 선정했습니다.
| 새 이름 | 평균 크기 (길이) | 평균 무게 | 가장 오래된 기록된 사례 (나이) |
|--------------------|---------------------------|--------------------|-------------------------------------|
| 집참새 (House Sparrow) | 약 16 cm | 약 24-39 g | 약 13년 (야생 기록) |
| 비둘기 (Rock Pigeon) | 약 32-37 cm | 약 238-380 g | 약 30년 (사육 기록) |
| 까치 (Eurasian Magpie) | 약 44-46 cm | 약 200-250 g | 약 21년 (야생 기록) |
| 참새 (American Robin) | 약 23-28 cm | 약 77 g | 약 14년 (야생 기록) |
| 찌르레기 (European Starling) | 약 20-23 cm | 약 75-90 g | 약 21년 (사육 기록) |
- 크기와 무게는 평균값 기준이며 환경에 따라 다소 변동이 있을 수 있습니다.
- 오래된 기록은 주로 야생 또는 사육 상태에서 관찰된 최대 생존 기간을 나타낸 것입니다.
필요시 특정 지역이나 다른 흔한 새들에 대해서도 추가 정보를 제공해 드릴 수 있습니다.
question = "마그누스 칼슨이 알리레자 피루즈자와의 경기에서 둔 수 순서를 작성하고 대안 전략을 제안하세요"
last_step = None
for step in graph.stream({"input": question}):
last_step = step
step_name, step_state = next(iter(step.items()))
print(step_name)
print("rolled out: ", step_state["root"].height)
print("---")start
rolled out: 1
---
expand
rolled out: 2
---
expand
rolled out: 3
---
expand
rolled out: 3
---
solution_node = last_step["expand"]["root"].get_best_solution()
best_trajectory = solution_node.get_trajectory(include_reflections=False)
print(best_trajectory[-1].content)현재 검색된 정보로는 마그누스 칼슨과 알리레자 피루즈자 간의 경기에서 칼슨이 20수 안에 승리한 사실은 확인되었으나, 구체적인 기보의 수순이나 상세한 수읽기 내용은 제공되지 않았습니다.
따라서 제가 보유한 일반적인 체스 지식을 바탕으로, 이와 같은 단기간 내 승리 사례에서 흔히 나타나는 패턴과 대안 전략을 제안드리겠습니다.
1. 일반적인 승리 수순 예시 (가상의 예시일 뿐입니다):
- e4 e5
- Nf3 Nc6
- Bb5 a6
- Ba4 Nf6
- O-O Be7
- Re1 b5
- Bb3 d6
- c3 O-O
- h3 Nb8
- d4 Nbd7
- c4 c6
- cxb5 axb5
- Nc3 Bb7
- Nh4 b4
- Nf5 bxc3
- bxc3 Re8
- Qf3 Bf8
- Bg5 h6
- Bh4 g6
(이후 칼슨이 상대의 약점을 정확히 노려 빠르게 승기를 잡았다고 가정)
2. 대안 전략 제안:
- 알리레자 피루즈자는 초반에 칼슨의 공격적 기세를 견제하지 못했던 점이 문제였을 수 있습니다. 따라서 다음과 같은 대안을 고려해볼 수 있습니다.
- 중앙 통제 강화: d4와 e5를 교환하거나 공격해서 칼슨의 공간 확장을 억제.
- 빠른 개발과 킹 안전 확보: 가능하면 퀵 캐슬링을 하여 킹의 안전을 꾀하며, 상대의 공격을 끊어낼 준비를 할 것.
- 기물 간 협력 유지: 특히 나이트와 비숍이 서로를 보호하며 상대의 공격로를 차단하도록 유도.
- 상황에 따른 반격 시도: 상대가 과도하게 공격적으로 나올 때는, 중후반에 역습을 노리는 전략도 효과적일 수 있음.
정확한 수순과 그에 따른 대안 전략 분석은 실제 기보를 참고하는 것이 가장 정확합니다. 만약 구체적인 기보가 필요하시면, 체스 전문 사이트(예: chess.com, lichess.org) 또는 해당 경기의 영상 강의에서 확인하는 것을 추천드립니다.
결론
LATS 구현을 완료하신 것을 축하합니다! 이는 복잡한 추론 작업을 해결하는 데 합리적으로 빠르고 효과적인 기법입니다. 위에서 관찰했을 몇 가지 참고 사항:
- 효과적이지만, 트리 롤아웃에는 추가 계산 시간이 소요될 수 있습니다. 프로덕션 앱에 포함하려면 중간 단계가 스트리밍되도록 하거나(사용자가 사고 과정을 보거나 중간 결과에 액세스할 수 있도록) 파인튜닝 데이터로 사용하여 단일 샷 정확도를 개선하고 긴 롤아웃을 피해야 합니다.
- 후보 선택 프로세스는 생성하는 보상만큼만 좋습니다. 여기서는 자기 반영만 사용하지만, 외부 피드백 소스(예: 코드 테스트 실행)가 있다면 위에서 언급한 위치에 통합해야 합니다.