feat: solution for task-002
This commit is contained in:
+92
-107
@@ -1,124 +1,109 @@
|
|||||||
I see several issues with the solution:
|
import questionary
|
||||||
|
from typing import TypedDict, List, Any, Optional
|
||||||
|
from langgraph.graph import StateGraph, START, END
|
||||||
|
from langgraph.checkpoint.memory import InMemorySaver
|
||||||
|
from langgraph.types import interrupt, Command
|
||||||
|
|
||||||
1. **Critical LLM connection issue**: The code connects to `localhost:1234` with fake credentials, but per the requirements, any LLM connection must use:
|
# 1. Описание состояния графа
|
||||||
- `base_url = "https://openrouter.ai/api/v1"`
|
class GraphState(TypedDict):
|
||||||
- `api_key = "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"`
|
foo: str
|
||||||
- `model = "baidu/cobuddy:free"`
|
human_value: Optional[str]
|
||||||
|
status: str
|
||||||
|
|
||||||
2. **Model name**: Using `'local-model'` as a placeholder is not valid.
|
# 2. Узел с прерыванием
|
||||||
|
def human_approval_node(state: GraphState) -> GraphState:
|
||||||
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 langchain_openai import ChatOpenAI
|
|
||||||
from langchain.tools import tool
|
|
||||||
from langchain.agents import create_agent, Tool
|
|
||||||
from pydantic import SecretStr
|
|
||||||
import json
|
|
||||||
|
|
||||||
# 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 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,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Системный промпт для субагента
|
|
||||||
system_prompt = """Ты - эксперт по ценам на продукты в России.
|
|
||||||
Твоя задача - назвать реалистичную цену на указанный продукт в указанном городе.
|
|
||||||
Отвечай ТОЛЬКО в формате таблицы Markdown:
|
|
||||||
| Продукт | Цена (руб.) | Магазин |
|
|
||||||
Не добавляй лишнего текста."""
|
|
||||||
|
|
||||||
price_agent = create_agent(
|
|
||||||
model=price_llm,
|
|
||||||
tools=[],
|
|
||||||
system_prompt=system_prompt,
|
|
||||||
)
|
|
||||||
return price_agent
|
|
||||||
|
|
||||||
# 3. Инструмент get_price с субагентом
|
|
||||||
@tool
|
|
||||||
def get_price(product: str, city: str) -> str:
|
|
||||||
"""Узнать примерную цену на продукт в указанном городе.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
product: Название продукта
|
|
||||||
city: Город, в котором ищем цену
|
|
||||||
"""
|
"""
|
||||||
print(f"get_price({{'product': '{product}', 'city': '{city}'}})")
|
Узел, который ставит выполнение на паузу и ждет ответа пользователя.
|
||||||
|
При вызове interrupt() граф останавливается.
|
||||||
|
Когда вызывается Command(resume=...), значение из resume попадает в результат interrupt().
|
||||||
|
"""
|
||||||
|
# Формируем payload для прерывания
|
||||||
|
interrupt_payload = {
|
||||||
|
"type": "confirm",
|
||||||
|
"question": "Уверены, что хотите продолжить?",
|
||||||
|
"options": ["approve", "reject"]
|
||||||
|
}
|
||||||
|
|
||||||
# Создаем субагента
|
# Вызываем interrupt.
|
||||||
price_agent = create_price_agent()
|
# Если граф возобновлен через Command(resume=...),
|
||||||
|
# переменная 'user_response' получит значение из resume.
|
||||||
|
user_response = interrupt(interrupt_payload)
|
||||||
|
|
||||||
# Формируем запрос
|
# Возвращаем обновленное состояние
|
||||||
query = f"Узнай цену на {product} в {city}. Отвечай в таблице Markdown."
|
return {
|
||||||
|
"human_value": user_response,
|
||||||
|
"status": "completed"
|
||||||
|
}
|
||||||
|
|
||||||
# Вызываем субагента
|
# 3. Сборка графа
|
||||||
result = price_agent.invoke({
|
def create_graph():
|
||||||
"messages": [
|
workflow = StateGraph(GraphState)
|
||||||
{"role": "human", "content": query}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
# Извлекаем ответ
|
workflow.add_node("approval_node", human_approval_node)
|
||||||
response_content = result['messages'][-1].content
|
workflow.add_edge(START, "approval_node")
|
||||||
|
workflow.add_edge("approval_node", END)
|
||||||
|
|
||||||
return response_content
|
# Используем InMemorySaver для поддержки чекпоинтов (необходим для прерываний)
|
||||||
|
memory = InMemorySaver()
|
||||||
|
return workflow.compile(checkpointer=memory)
|
||||||
|
|
||||||
# 4. Главный агент
|
def run_workflow():
|
||||||
main_llm = ChatOpenAI(
|
graph = create_graph()
|
||||||
model='baidu/cobuddy:free',
|
config = {"configurable": {"thread_id": "123"}}
|
||||||
base_url='https://openrouter.ai/api/v1',
|
initial_state = {"foo": "initial_data", "human_value": None, "status": "running"}
|
||||||
api_key=SecretStr('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'),
|
|
||||||
temperature=0.7,
|
|
||||||
)
|
|
||||||
|
|
||||||
main_agent = create_agent(
|
# Запуск графа
|
||||||
model=main_llm,
|
print("--- Starting Graph Execution ---")
|
||||||
tools=[get_price],
|
|
||||||
system_prompt='Ты помощник по планированию покупок. Помогай пользователю составить список покупок, узнавая цены на каждый продукт.',
|
|
||||||
)
|
|
||||||
|
|
||||||
# 5. Запрос и вывод
|
# Первый запуск (до прерывания)
|
||||||
def format_message(message) -> str:
|
# Используем stream_mode="updates" или "values".
|
||||||
if message.content:
|
# При interrupt в потоке появится специальный элемент.
|
||||||
return message.content
|
events = graph.stream(initial_command_or_state(initial_state), config, stream_mode="updates")
|
||||||
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)
|
|
||||||
|
|
||||||
# Задаем вопрос
|
final_state = None
|
||||||
query = "Помоги составить список покупок: молоко, хлеб, яблоки. Я нахожусь в Казани."
|
|
||||||
|
|
||||||
print(f"\nЗапрос: {query}\n")
|
# Итерируемся по событиям
|
||||||
|
for event in events:
|
||||||
|
# Проверяем наличие прерывания в потоке через ключ __interrupt__
|
||||||
|
if "__interrupt__" in event:
|
||||||
|
# Извлекаем payload из прерывания
|
||||||
|
# В LangGraph interrupt создает элемент в списке interrupts задачи
|
||||||
|
interrupt_info = event["__interrupt__"][0].value
|
||||||
|
|
||||||
# Вызываем агента
|
print("\n--- Interrupt Detected ---")
|
||||||
answer = main_agent.invoke({
|
print(interrupt_info)
|
||||||
"messages": [
|
print(f"!!! {interrupt_info['type'].upper()} !!!")
|
||||||
{"role": "human", "content": query}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
# Выводим все сообщения
|
# 4. Сбор ответа пользователя через questionary
|
||||||
for msg in answer['messages']:
|
user_choice = questionary.select(
|
||||||
print(format_message(msg))
|
interrupt_info["question"],
|
||||||
print()
|
choices=interrupt_info["options"]
|
||||||
|
).ask()
|
||||||
|
|
||||||
print("\nФинальный ответ:")
|
print(f"> Received an input from the interrupt: {user_choice}")
|
||||||
print(answer['messages'][-1].content)
|
|
||||||
|
|
||||||
APPROVED
|
# 5. Возобновление графа через Command(resume=...)
|
||||||
|
# Передаем выбранный ответ обратно в граф
|
||||||
|
resume_events = graph.stream(
|
||||||
|
Command(resume=user_choice),
|
||||||
|
config,
|
||||||
|
stream_mode="updates"
|
||||||
|
)
|
||||||
|
|
||||||
|
for resume_event in resume_events:
|
||||||
|
final_state = resume_event
|
||||||
|
else:
|
||||||
|
# Обычное обновление узлов
|
||||||
|
final_state = event
|
||||||
|
|
||||||
|
# Вывод итогового состояния (из последнего события или snapshot)
|
||||||
|
print("\n--- Final State ---")
|
||||||
|
snapshot = graph.get_state(config)
|
||||||
|
print(snapshot.values)
|
||||||
|
|
||||||
|
def initial_command_or_state(initial_state):
|
||||||
|
"""Вспомогательная функция для корректного старта."""
|
||||||
|
return initial_state
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_workflow()
|
||||||
Reference in New Issue
Block a user