From 60df058f254c7540895f1e5b8954b887dadf272a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=D0=B8=D1=80=D0=BE=D0=B2=D0=B8=D1=87=20=D0=91=D0=B0?= =?UTF-8?q?=D0=B1=D0=B0=D0=B9=D0=BA=D0=B8=D0=BD?= Date: Mon, 18 May 2026 09:03:41 +0000 Subject: [PATCH] feat: solution for task-002 --- solutions/task-002/solution.py | 183 +++++++++++++++++++-------------- 1 file changed, 108 insertions(+), 75 deletions(-) diff --git a/solutions/task-002/solution.py b/solutions/task-002/solution.py index a7a62b5..ad94d19 100644 --- a/solutions/task-002/solution.py +++ b/solutions/task-002/solution.py @@ -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 langgraph.graph import StateGraph, START, END -from langgraph.types import interrupt, Command -from langgraph.checkpoint.memory import InMemorySaver -import questionary +from langchain_openai import ChatOpenAI +from langchain.tools import tool +from langchain.agents import create_agent, Tool +from pydantic import SecretStr +import json -# 1. Состояние графа -class GraphState(TypedDict): - human_value: str - foo: str +# 1. Подключение к локальной модели +llm = ChatOpenAI( + model='baidu/cobuddy:free', + base_url='https://openrouter.ai/api/v1', + api_key=SecretStr('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'), + temperature=0.7, +) -# 2. Узел с прерыванием -def interrupt_node(state: GraphState) -> Command: - # Если human_value уже заполнен - это возобновление, возвращаем состояние - if state.get("human_value"): - return state +# 2. Функция для создания субагента +def create_price_agent(): + """Создает субагент для получения цен на продукты""" + price_llm = ChatOpenAI( + model='baidu/cobuddy:free', + base_url='https://openrouter.ai/api/v1', + api_key=SecretStr('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'), + temperature=0.3, + ) - # Первое прерывание - запрашиваем подтверждение - payload = { - "type": "confirm", - "question": "Уверены, что хотите продолжить?", - "allow_responds": ["approve", "reject"] - } - return interrupt(payload) + # Системный промпт для субагента + system_prompt = """Ты - эксперт по ценам на продукты в России. +Твоя задача - назвать реалистичную цену на указанный продукт в указанном городе. +Отвечай ТОЛЬКО в формате таблицы Markdown: +| Продукт | Цена (руб.) | Магазин | +Не добавляй лишнего текста.""" -# 3. Сборка графа -def build_graph(): - workflow = StateGraph(GraphState) - workflow.add_node("interrupt_node", interrupt_node) - workflow.add_edge(START, "interrupt_node") - workflow.add_edge("interrupt_node", END) - return workflow.compile(checkpointer=InMemorySaver()) + price_agent = create_agent( + model=price_llm, + tools=[], + system_prompt=system_prompt, + ) + return price_agent -# 4. Запуск с обработкой прерывания -def run(): - graph = build_graph() - config = {"configurable": {"thread_id": "1"}} +# 3. Инструмент get_price с субагентом +@tool +def get_price(product: str, city: str) -> str: + """Узнать примерную цену на продукт в указанном городе. - # Начальное состояние - initial_state = {"human_value": "", "foo": "начальное значение"} + Args: + product: Название продукта + city: Город, в котором ищем цену + """ + print(f"get_price({{'product': '{product}', 'city': '{city}'}})") - # Запускаем граф - for chunk in graph.stream(initial_state, config): - 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) + # Создаем субагента + price_agent = create_price_agent() - # Итоговое состояние - final_state = graph.get_state(config).values - print("\nИтоговое состояние:", final_state) + # Формируем запрос + query = f"Узнай цену на {product} в {city}. Отвечай в таблице Markdown." + + # Вызываем субагента + result = price_agent.invoke({ + "messages": [ + {"role": "human", "content": query} + ] + }) + + # Извлекаем ответ + response_content = result['messages'][-1].content + + return response_content -if __name__ == "__main__": - run() +# 4. Главный агент +main_llm = ChatOpenAI( + model='baidu/cobuddy:free', + base_url='https://openrouter.ai/api/v1', + api_key=SecretStr('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'), + temperature=0.7, +) -**Что исправлено**: -1. Узел `interrupt_node` теперь проверяет, заполнено ли `human_value`. Если да — это возобновление, возвращаем состояние без нового прерывания. -2. При возобновлении используется `Command(resume=interrupt_value, update={"human_value": answer})` для обновления состояния. -3. Добавлен вывод ответа для лучшей видимости. +main_agent = create_agent( + model=main_llm, + tools=[get_price], + system_prompt='Ты помощник по планированию покупок. Помогай пользователю составить список покупок, узнавая цены на каждый продукт.', +) -**APPROVED** \ No newline at end of file +# 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 \ No newline at end of file