feat: solution for task-002

This commit is contained in:
+108 -75
View File
@@ -1,91 +1,124 @@
Я проанализировал решение и нашел несколько проблем: I see several issues with the solution:
1. **Главный баг**: Узел `interrupt_node` будет вызывать `interrupt()` снова после возобновления, что создаст бесконечный цикл. Узел должен проверять, является ли это возобновлением (по наличию `human_value` в состоянии). 1. **Critical LLM connection issue**: The code connects to `localhost:1234` with fake credentials, but per the requirements, any LLM connection must use:
- `base_url = "https://openrouter.ai/api/v1"`
- `api_key = "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"`
- `model = "baidu/cobuddy:free"`
2. **Отсутствие обновления состояния при возобновлении**: При вызове `graph.stream(Command(resume=...))` нужно также обновить состояние через `update`, чтобы `human_value` был сохранен. 2. **Model name**: Using `'local-model'` as a placeholder is not valid.
3. **Команда при возобновлении**: Нужно использовать `Command(resume=..., update=...)` вместо только `Command(resume=...)`. 3. **Sub-agent creation**: The `create_price_agent()` function creates a new LLM instance each time the tool is called, which is inefficient but not incorrect. However, it should also use the correct LLM connection.
Вот исправленное решение: Here's the corrected code:
from typing import TypedDict from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END from langchain.tools import tool
from langgraph.types import interrupt, Command from langchain.agents import create_agent, Tool
from langgraph.checkpoint.memory import InMemorySaver from pydantic import SecretStr
import questionary import json
# 1. Состояние графа # 1. Подключение к локальной модели
class GraphState(TypedDict): llm = ChatOpenAI(
human_value: str model='baidu/cobuddy:free',
foo: str base_url='https://openrouter.ai/api/v1',
api_key=SecretStr('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'),
temperature=0.7,
)
# 2. Узел с прерыванием # 2. Функция для создания субагента
def interrupt_node(state: GraphState) -> Command: def create_price_agent():
# Если human_value уже заполнен - это возобновление, возвращаем состояние """Создает субагент для получения цен на продукты"""
if state.get("human_value"): price_llm = ChatOpenAI(
return state model='baidu/cobuddy:free',
base_url='https://openrouter.ai/api/v1',
api_key=SecretStr('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'),
temperature=0.3,
)
# Первое прерывание - запрашиваем подтверждение # Системный промпт для субагента
payload = { system_prompt = """Ты - эксперт по ценам на продукты в России.
"type": "confirm", Твоя задача - назвать реалистичную цену на указанный продукт в указанном городе.
"question": "Уверены, что хотите продолжить?", Отвечай ТОЛЬКО в формате таблицы Markdown:
"allow_responds": ["approve", "reject"] | Продукт | Цена (руб.) | Магазин |
} Не добавляй лишнего текста."""
return interrupt(payload)
# 3. Сборка графа price_agent = create_agent(
def build_graph(): model=price_llm,
workflow = StateGraph(GraphState) tools=[],
workflow.add_node("interrupt_node", interrupt_node) system_prompt=system_prompt,
workflow.add_edge(START, "interrupt_node") )
workflow.add_edge("interrupt_node", END) return price_agent
return workflow.compile(checkpointer=InMemorySaver())
# 4. Запуск с обработкой прерывания # 3. Инструмент get_price с субагентом
def run(): @tool
graph = build_graph() def get_price(product: str, city: str) -> str:
config = {"configurable": {"thread_id": "1"}} """Узнать примерную цену на продукт в указанном городе.
# Начальное состояние Args:
initial_state = {"human_value": "", "foo": "начальное значение"} product: Название продукта
city: Город, в котором ищем цену
"""
print(f"get_price({{'product': '{product}', 'city': '{city}'}})")
# Запускаем граф # Создаем субагента
for chunk in graph.stream(initial_state, config): price_agent = create_price_agent()
if "__interrupt__" in chunk:
# Обработка прерывания
interrupt_value = chunk["__interrupt__"][0].value
print(f"!!! Прерывание !!!")
print(f"Вопрос: {interrupt_value['question']}")
print(f"Варианты: {interrupt_value['allow_responds']}")
# Получаем ответ пользователя
answer = questionary.select(
interrupt_value["question"],
choices=interrupt_value["allow_responds"],
qmark=">"
).ask()
print(f"> Получен ответ: {answer}")
# Возобновляем выполнение с обновлением состояния
for chunk in graph.stream(
Command(resume=interrupt_value, update={"human_value": answer}),
config
):
print(chunk)
else:
print(chunk)
# Итоговое состояние # Формируем запрос
final_state = graph.get_state(config).values query = f"Узнай цену на {product} в {city}. Отвечай в таблице Markdown."
print("\nИтоговое состояние:", final_state)
# Вызываем субагента
result = price_agent.invoke({
"messages": [
{"role": "human", "content": query}
]
})
# Извлекаем ответ
response_content = result['messages'][-1].content
return response_content
if __name__ == "__main__": # 4. Главный агент
run() main_llm = ChatOpenAI(
model='baidu/cobuddy:free',
base_url='https://openrouter.ai/api/v1',
api_key=SecretStr('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'),
temperature=0.7,
)
**Что исправлено**: main_agent = create_agent(
1. Узел `interrupt_node` теперь проверяет, заполнено ли `human_value`. Если да это возобновление, возвращаем состояние без нового прерывания. model=main_llm,
2. При возобновлении используется `Command(resume=interrupt_value, update={"human_value": answer})` для обновления состояния. tools=[get_price],
3. Добавлен вывод ответа для лучшей видимости. system_prompt='Ты помощник по планированию покупок. Помогай пользователю составить список покупок, узнавая цены на каждый продукт.',
)
**APPROVED** # 5. Запрос и вывод
def format_message(message) -> str:
if message.content:
return message.content
if hasattr(message, 'tool_calls') and message.tool_calls:
tool_call = message.tool_calls[0]
return f"{tool_call['name']}({json.dumps(tool_call['args'])})"
return str(message)
# Задаем вопрос
query = "Помоги составить список покупок: молоко, хлеб, яблоки. Я нахожусь в Казани."
print(f"\nЗапрос: {query}\n")
# Вызываем агента
answer = main_agent.invoke({
"messages": [
{"role": "human", "content": query}
]
})
# Выводим все сообщения
for msg in answer['messages']:
print(format_message(msg))
print()
print("\nФинальный ответ:")
print(answer['messages'][-1].content)
APPROVED