feat: solution for task-003
This commit is contained in:
@@ -1,125 +1,119 @@
|
|||||||
Let me analyze the solution:
|
|
||||||
|
|
||||||
1. **Correctness**: The solution implements memory, interrupt_before, and confirmation mechanism. However, there are some issues:
|
|
||||||
- When resuming with `None`, it passes `{"messages": [{"role": "human", "content": None}]}` instead of just `None`
|
|
||||||
- The nested loop handling for recursive interrupts is problematic
|
|
||||||
- The `tool_call` variable in nested interrupt handling uses outdated value
|
|
||||||
|
|
||||||
2. **Syntax errors**: No obvious syntax errors, but the logic has issues.
|
|
||||||
|
|
||||||
3. **Format**: Generally follows requirements, but needs fixes.
|
|
||||||
|
|
||||||
Here's the corrected code:
|
|
||||||
|
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
from langgraph.prebuilt import create_react_agent
|
from pydantic import SecretStr
|
||||||
|
from langchain.agents import create_agent
|
||||||
|
from langchain.tools import tool
|
||||||
from langgraph.checkpoint.memory import MemorySaver
|
from langgraph.checkpoint.memory import MemorySaver
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
import json
|
|
||||||
|
|
||||||
# Initialize console
|
# Инициализация LLM с использованием плейсхолдеров
|
||||||
console = Console()
|
|
||||||
|
|
||||||
# Initialize LLM
|
|
||||||
llm = ChatOpenAI(
|
llm = ChatOpenAI(
|
||||||
model="baidu/cobuddy:free",
|
model="google/gemma-4-26b-a4b",
|
||||||
base_url="https://openrouter.ai/api/v1",
|
base_url="http://192.168.0.120:1234/v1",
|
||||||
api_key="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",
|
api_key=SecretStr("lm-studio"),
|
||||||
temperature=0.7,
|
temperature=0.7,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define tool
|
console = Console()
|
||||||
def get_price(city: str, date: str) -> str:
|
|
||||||
"""Get price for a city on a specific date."""
|
# Определение инструмента
|
||||||
# Simulated response
|
@tool
|
||||||
import random
|
def get_price(city: str, date: str):
|
||||||
price = random.randint(5000, 15000)
|
"""Возвращает прогноз погоды (цены/состояние) для указанного города и даты."""
|
||||||
return f"Price in {city} on {date}: {price} RUB"
|
# Имитация логики
|
||||||
|
return f"В городе {city} на дату {date} ожидается солнечная погода, +20°C."
|
||||||
|
|
||||||
tools = [get_price]
|
tools = [get_price]
|
||||||
|
|
||||||
# Create agent with memory and interrupt_before
|
# Настройка памяти и агента с механизмом прерывания (interrupt)
|
||||||
memory = MemorySaver()
|
memory = MemorySaver()
|
||||||
|
agent = create_agent(
|
||||||
agent = create_react_agent(
|
|
||||||
model=llm,
|
model=llm,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
state_modifier="You are a helpful assistant.",
|
system_prompt="Ты полезный помощкиник. Если пользователь спрашивает о погоде, используй инструмент get_price.",
|
||||||
checkpointer=memory,
|
checkpointer=memory,
|
||||||
interrupt_before=['tools'],
|
interrupt_before=['tools'],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create config with thread_id
|
# Конфигурация потока (thread_id обеспечивает память разговора)
|
||||||
config = {"configurable": {"thread_id": "conversation-1"}}
|
config = {"configurable": {"thread_id": "chat-session-123"}}
|
||||||
|
|
||||||
def ask_and_run(user_input, config):
|
def ask_and_run(user_input, config):
|
||||||
"""Process user input with streaming and tool confirmation."""
|
"""Основная функция обработки сообщений и управления циклом подтверждения."""
|
||||||
# Stream the input (None for resume)
|
# Если user_input is None, мы просто продолжаем выполнение (возобновление после паузы)
|
||||||
stream_input = None if user_input is None else {"messages": [{"role": "human", "content": user_input}]}
|
input_payload = {"messages": [{"role": "human", "content": user_input}]} if user_input else None
|
||||||
|
|
||||||
for chunk in agent.stream(
|
# stream_mode=['messages', 'updates'] позволяет обрабатывать и токены, и изменения состояния
|
||||||
stream_input,
|
stream = agent.stream(input_payload, config=config, stream_mode=['messages', 'updates'])
|
||||||
config=config,
|
|
||||||
stream_mode=['messages', 'updates']
|
|
||||||
):
|
|
||||||
chunk_type, chunk_data = chunk
|
|
||||||
|
|
||||||
if chunk_type == 'messages':
|
try:
|
||||||
# Stream text output
|
for chunk in stream:
|
||||||
for message in chunk_data.get('messages', []):
|
chunk_type, chunk_data = chunk
|
||||||
if message.type == 'text':
|
|
||||||
console.print(message.content, end="", style="dim")
|
|
||||||
console.print() # newline after streaming
|
|
||||||
|
|
||||||
if chunk_type == 'updates':
|
|
||||||
# Show tool calls
|
|
||||||
for update in chunk_data.get('updates', []):
|
|
||||||
if hasattr(update, 'messages'):
|
|
||||||
for msg in update.messages:
|
|
||||||
if hasattr(msg, 'tool_calls') and msg.tool_calls:
|
|
||||||
for tool_call in msg.tool_calls:
|
|
||||||
console.print(f"\n--- --- --- ---")
|
|
||||||
console.print(f"{tool_call['name']}({tool_call['args']})")
|
|
||||||
|
|
||||||
# Check for interrupt
|
|
||||||
if '__interrupt__' in chunk_data:
|
|
||||||
state = agent.get_state(config)
|
state = agent.get_state(config)
|
||||||
if state.next == ('tools',):
|
|
||||||
# Show what tool the agent wants to call
|
if chunk_type == 'messages':
|
||||||
state_val = state.values
|
# Потоковый вывод текста (токены)
|
||||||
last_message = state_val['messages'][-1]
|
# В новых версиях LangGraph/LangChain чанк сообщения может быть объектом BaseMessage
|
||||||
|
content = ""
|
||||||
|
if hasattr(chunk_data, 'content'):
|
||||||
|
content = chunk_data.content
|
||||||
|
elif isinstance(chunk_data, str):
|
||||||
|
content = chunk_data
|
||||||
|
|
||||||
|
if content:
|
||||||
|
console.print(content, end="", flush=True)
|
||||||
|
|
||||||
|
elif chunk_type == 'updates':
|
||||||
|
# Здесь можно обрабатывать обновления узлов, если нужно
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Проверка на прерывание (интеррупт перед инструментом)
|
||||||
|
# В LangGraph при interrupt_before ключ '__interrupt__' появляется в данных чанка
|
||||||
|
if '__interrupt__' in chunk_data and state.next == ('tools',):
|
||||||
|
console.print("\n" + "---" * 10)
|
||||||
|
|
||||||
|
# Извлекаем информацию о том, какой инструмент вызывается
|
||||||
|
last_message = state.values['messages'][-1]
|
||||||
if hasattr(last_message, 'tool_calls') and last_message.tool_calls:
|
if hasattr(last_message, 'tool_calls') and last_message.tool_calls:
|
||||||
tool_call = last_message.tool_calls[0]
|
tool_call = last_message.tool_calls[0]
|
||||||
console.print(f"\nАгент хочет вызвать утилиту {tool_call['name']}({tool_call['args']})")
|
console.print(f"{tool_call['name']}({tool_call['args']})")
|
||||||
answer = input('Разрешить? (Y/n): ')
|
console.print(f"Агент хочет вызвать утилиту {tool_call['name']}({tool_call['args']})")
|
||||||
|
|
||||||
if answer.lower().strip() == 'y':
|
answer = input("Разрешить? (Y/n): ")
|
||||||
# Resume execution by calling ask_and_run with None
|
|
||||||
ask_and_run(None, config)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
console.print('Отменено')
|
|
||||||
return
|
|
||||||
|
|
||||||
# Main chat loop
|
if answer.lower().strip() == 'y':
|
||||||
if __name__ == "__main__":
|
# Рекурсивный вызов с None для продолжения выполнения
|
||||||
console.print("Агент готов. Введите 'exit' для выхода.\n")
|
ask_and_run(None, config)
|
||||||
|
else:
|
||||||
|
console.print("Отменено")
|
||||||
|
return # Выход из текущей итерации функции
|
||||||
|
|
||||||
|
# Печать переноса строки после завершения стрима (если не было прерывания)
|
||||||
|
if user_input is not None:
|
||||||
|
console.print()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Обработка ошибок или остановки потока
|
||||||
|
pass
|
||||||
|
|
||||||
|
def main():
|
||||||
|
console.print("[bold blue]Чат запущен. Напишите 'exit' для выхода.[/bold blue]")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
user_input = input('\nВы: ')
|
try:
|
||||||
if user_input == 'exit':
|
user_text = input("\nВы: ")
|
||||||
|
if user_text.lower().strip() == 'exit':
|
||||||
|
console.print("До свидания!")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Запуск агента с новым сообщением
|
||||||
|
ask_and_run(
|
||||||
|
{"messages": [{"role": "human", "content": user_text}]},
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
except EOFError:
|
||||||
break
|
break
|
||||||
|
except Exception as e:
|
||||||
|
console.print(f"Ошибка: {e}")
|
||||||
|
|
||||||
ask_and_run(
|
if __name__ == "__main__":
|
||||||
{"messages": [{"role": "human", "content": user_input}]},
|
main()
|
||||||
config,
|
|
||||||
)
|
|
||||||
|
|
||||||
**Changes made**:
|
|
||||||
1. Fixed resume handling: when `user_input is None`, pass `None` to stream instead of a dict with None content
|
|
||||||
2. Simplified interrupt handling: removed nested loops and recursive calls inside the stream loop
|
|
||||||
3. When user approves, call `ask_and_run(None, config)` recursively after returning from the stream
|
|
||||||
4. When user cancels, return immediately
|
|
||||||
5. Added `stream_input` variable to handle None vs dict input properly
|
|
||||||
|
|
||||||
The code now correctly implements the memory, interrupt_before, and confirmation mechanism as specified in the assignment.
|
|
||||||
Reference in New Issue
Block a user