feat: solution for task-002
This commit is contained in:
+105
-72
@@ -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(
|
query = f"Узнай цену на {product} в {city}. Отвечай в таблице Markdown."
|
||||||
interrupt_value["question"],
|
|
||||||
choices=interrupt_value["allow_responds"],
|
|
||||||
qmark=">"
|
|
||||||
).ask()
|
|
||||||
|
|
||||||
print(f"> Получен ответ: {answer}")
|
# Вызываем субагента
|
||||||
|
result = price_agent.invoke({
|
||||||
|
"messages": [
|
||||||
|
{"role": "human", "content": query}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
# Возобновляем выполнение с обновлением состояния
|
# Извлекаем ответ
|
||||||
for chunk in graph.stream(
|
response_content = result['messages'][-1].content
|
||||||
Command(resume=interrupt_value, update={"human_value": answer}),
|
|
||||||
config
|
|
||||||
):
|
|
||||||
print(chunk)
|
|
||||||
else:
|
|
||||||
print(chunk)
|
|
||||||
|
|
||||||
# Итоговое состояние
|
return response_content
|
||||||
final_state = graph.get_state(config).values
|
|
||||||
print("\nИтоговое состояние:", final_state)
|
|
||||||
|
|
||||||
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
|
||||||
Reference in New Issue
Block a user