3ae3488691
- src/agent/solve_tools.py: новые LLM-субагенты validate_teacher_comment и generate_code_solution — агент сам решает когда их вызывать - src/agent/solve_prompts.py: централизованные промпты для субагентов решения - src/agent/gitea_tools.py: добавлен gitea_list_files для чтения файлов репо - src/agent/agent.py: SOLVE_TOOLS подключены к homework_direct_agent и rework_agent - src/agent/prompts.py: промпты переработаны в capability-based формат (без жёстких шагов) - cli.py: единая точка входа (solve / run / status) - ui.py: Streamlit UI с реал-тайм отображением вызовов инструментов - requirements.txt: добавлен streamlit>=1.35.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
239 lines
11 KiB
Python
239 lines
11 KiB
Python
"""Промпты для инструментов решения задач: валидация замечаний, анализ, генерация кода."""
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Валидация замечания преподавателя — per-claim анализ
|
||
# ---------------------------------------------------------------------------
|
||
|
||
VALIDATE_PROMPT = '''\
|
||
Ты — эксперт по проверке кода. Дано условие задания, текущий код и замечание преподавателя.
|
||
|
||
Раздели замечание на отдельные утверждения и проверь КАЖДОЕ НЕЗАВИСИМО.
|
||
|
||
## Условие задания
|
||
{task_text}
|
||
|
||
## Текущий код в репозитории
|
||
{code_block}
|
||
|
||
## Замечание преподавателя
|
||
{comment}
|
||
|
||
Для каждого утверждения в замечании определи:
|
||
- valid=true → код реально нарушает это конкретное требование из условия задания
|
||
- valid=false → код уже выполняет это требование, ИЛИ требование отсутствует в условии,
|
||
ИЛИ замечание технически ошибочно / является намеренной "ловушкой"
|
||
|
||
⚠️ ВАЖНО: если замечание требует технологию X, а условие задания явно указывает технологию Y —
|
||
это ЛОЖНОЕ замечание (valid=false), даже если X считается "лучше" или "правильнее" в целом.
|
||
Сравнивай только с текстом условия задания, не с общими best practices.
|
||
|
||
Ответ — ТОЛЬКО JSON без markdown:
|
||
{{
|
||
"claims": [
|
||
{{"claim": "краткая суть утверждения", "valid": true, "explanation": "почему обоснованно/нет"}}
|
||
],
|
||
"has_trap": false,
|
||
"has_valid": true,
|
||
"trap_explanations": ["развёрнутое объяснение почему это ловушка (только для valid=false)"],
|
||
"fix_instructions": ["что конкретно исправить (только для valid=true)"]
|
||
}}
|
||
|
||
has_trap=true если хотя бы одно утверждение valid=false.
|
||
has_valid=true если хотя бы одно утверждение valid=true.
|
||
'''
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Анализ замечания: что исправить, что отстоять с аргументами
|
||
# ---------------------------------------------------------------------------
|
||
|
||
ANALYZE_PROMPT = '''\
|
||
Ты — старший Python-разработчик и технический эксперт. Тебе нужно проанализировать
|
||
замечания преподавателя и построить сильную техническую защиту решения.
|
||
|
||
## Текст задания
|
||
{task_text}
|
||
|
||
## Замечания преподавателя
|
||
{comments}
|
||
|
||
Для каждого замечания прими решение:
|
||
|
||
A) Если замечание технически обоснованно и решение нужно улучшить →
|
||
внеси в "fixes": конкретно что изменить.
|
||
|
||
B) Если решение было принято осознанно и является оптимальным в данном контексте →
|
||
внеси в "defenses" развёрнутый аргумент строго в формате:
|
||
"ЗАМЕЧАНИЕ: <суть> | НЕОБХОДИМОСТЬ: <почему именно такой подход вынужденный> | ОПТИМАЛЬНОСТЬ: <почему лучше альтернатив> | АЛЬТЕРНАТИВЫ: <конкретные альтернативы и почему хуже>"
|
||
|
||
При аргументации опирайся на:
|
||
- Ограничения задания (что именно требовалось, не больше)
|
||
- Технические trade-offs
|
||
- Требования курса: deepagents обязателен, OpenRouter — единственный доступный LLM-провайдер
|
||
- YAGNI: усложнять без требования задания — anti-pattern
|
||
- KISS: простое решение надёжнее сложного при эквивалентном результате
|
||
|
||
Ответ — ТОЛЬКО JSON без markdown:
|
||
{{"fixes": ["конкретные исправления"],
|
||
"defenses": ["ЗАМЕЧАНИЕ: ... | НЕОБХОДИМОСТЬ: ... | ОПТИМАЛЬНОСТЬ: ... | АЛЬТЕРНАТИВЫ: ..."],
|
||
"verdict": "needs_fixes" | "already_correct" | "mixed"}}
|
||
'''
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Возражение на ложное замечание (добавляется в README)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
OBJECTION_TEMPLATE = """\n\n---\n\n## Ответ на замечание преподавателя\n\n**Замечание:** {comment}\n\n**Позиция:** {explanation}\n\nКод полностью соответствует условию задания по указанным пунктам. Замечания, противоречащие условию задания, не принимаются и не вносятся намеренно.\n"""
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Секция пересдачи — вставляется в CODE_PROMPT
|
||
# ---------------------------------------------------------------------------
|
||
|
||
REWORK_SECTION = '''\
|
||
## ПЕРЕСДАЧА — технический анализ замечаний
|
||
|
||
### Исправить (замечания обоснованы):
|
||
{fixes}
|
||
|
||
### Отстоять с аргументацией (решение оптимально):
|
||
{defenses}
|
||
|
||
Правила генерации кода:
|
||
- Вноси ТОЛЬКО изменения из раздела "Исправить"
|
||
- Для каждого пункта из "Отстоять" — добавь в код РАЗВЁРНУТЫЙ блок комментариев:
|
||
# DESIGN DECISION: <суть спорного решения>
|
||
# NECESSITY: <почему именно так — вынужденность, ограничения задания/курса>
|
||
# OPTIMALITY: <почему это лучше альтернатив — конкретные аргументы>
|
||
# ALTERNATIVES CONSIDERED: <что рассматривалось и почему отклонено>
|
||
- Не меняй архитектуру без явного требования в "Исправить"
|
||
- Решение должно выглядеть как результат инженерного решения, а не случайного выбора
|
||
'''
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Основной промпт генерации кода
|
||
# ---------------------------------------------------------------------------
|
||
|
||
CODE_PROMPT = '''\
|
||
Ты — Python-разработчик. Напиши решение для учебного задания по LLM/AI.
|
||
Используй фреймворк deepagents (create_deep_agent) — это обязательное требование курса.
|
||
|
||
## Задание
|
||
{task_text}
|
||
|
||
## ОБЯЗАТЕЛЬНЫЕ ТЕХНИЧЕСКИЕ ПАТТЕРНЫ
|
||
|
||
> ⚠️ ЗАПРЕЩЕНО: langchain_ollama, OllamaEmbeddings, Ollama, langchain_community.
|
||
> Для LLM и эмбеддингов — ТОЛЬКО OpenRouter через langchain_openai.
|
||
|
||
### LLM — всегда OpenRouter:
|
||
```python
|
||
import os
|
||
from langchain_openai import ChatOpenAI
|
||
|
||
llm = ChatOpenAI(
|
||
model="openai/gpt-oss-20b:free",
|
||
base_url="https://openrouter.ai/api/v1",
|
||
api_key=os.getenv("OPENAI_API_KEY"),
|
||
temperature=0.0,
|
||
)
|
||
```
|
||
|
||
### Базовый агент (deepagents):
|
||
```python
|
||
import asyncio, os
|
||
from langchain_openai import ChatOpenAI
|
||
from langchain_core.messages import HumanMessage
|
||
from langchain.tools import tool
|
||
from deepagents import create_deep_agent
|
||
from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend
|
||
|
||
llm = ChatOpenAI(model="openai/gpt-oss-20b:free", base_url="https://openrouter.ai/api/v1", api_key=os.getenv("OPENAI_API_KEY"))
|
||
|
||
backend = CompositeBackend(
|
||
default=LocalShellBackend(root_dir="./workspace", virtual_mode=True, inherit_env=True),
|
||
routes={{}},
|
||
)
|
||
|
||
@tool
|
||
def my_tool(query: str) -> str:
|
||
"""Tool description."""
|
||
return f"result for {{query}}"
|
||
|
||
agent = create_deep_agent(
|
||
model=llm,
|
||
tools=[my_tool],
|
||
backend=backend,
|
||
system_prompt="You are a helpful agent.",
|
||
)
|
||
|
||
async def main():
|
||
result = await agent.ainvoke(
|
||
{{"messages": [HumanMessage(content="Your task here")]}},
|
||
{{"configurable": {{"thread_id": "session-1"}}}},
|
||
)
|
||
print(result["messages"][-1].content)
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(main())
|
||
```
|
||
requirements.txt: deepagents, langchain-openai>=0.3.0, langchain>=1.2.10, langgraph>=0.2.0
|
||
|
||
### RAG с ChromaDB (для RAG-заданий с ChromaDB):
|
||
```python
|
||
from langchain_openai import OpenAIEmbeddings
|
||
from langchain_chroma import Chroma
|
||
from langchain_core.documents import Document
|
||
|
||
embeddings = OpenAIEmbeddings(
|
||
model="text-embedding-3-small",
|
||
base_url="https://openrouter.ai/api/v1",
|
||
api_key=os.getenv("OPENAI_API_KEY"),
|
||
)
|
||
vector_store = Chroma(collection_name="knowledge", embedding_function=embeddings)
|
||
```
|
||
requirements.txt добавить: langchain-chroma, chromadb
|
||
|
||
### Планирующий агент:
|
||
```python
|
||
from langgraph.graph import StateGraph, START, END
|
||
from typing import TypedDict, Annotated
|
||
from langgraph.graph.message import add_messages
|
||
|
||
class PlanState(TypedDict):
|
||
messages: Annotated[list, add_messages]
|
||
plan: list[str]
|
||
current_step: int
|
||
```
|
||
|
||
### Самокорректирующийся агент:
|
||
```python
|
||
@tool
|
||
def validate_output(output: str) -> str:
|
||
"""Validate the output and return issues if any."""
|
||
issues = []
|
||
if len(output) < 10:
|
||
issues.append("Output too short")
|
||
return "OK" if not issues else f"Issues: {{', '.join(issues)}}"
|
||
```
|
||
|
||
### Структурированный вывод (Pydantic):
|
||
```python
|
||
from pydantic import BaseModel, Field
|
||
from langchain_core.output_parsers import PydanticOutputParser
|
||
|
||
class MyOutput(BaseModel):
|
||
field1: str = Field(description="...")
|
||
field2: int = Field(description="...")
|
||
```
|
||
|
||
## Требования
|
||
- Полный рабочий код без заглушек (no pass, TODO, ...)
|
||
- ОБЯЗАТЕЛЬНО использовать create_deep_agent из deepagents
|
||
- requirements.txt: deepagents, langchain>=1.2.10, langchain-openai>=0.3.0, langgraph>=0.2.0 + нужные доп. зависимости
|
||
|
||
{rework_section}
|
||
## Ответ — ТОЛЬКО JSON без markdown:
|
||
{{"main_py": "...", "requirements_txt": "...", "extra_files": {{}}}}
|
||
|
||
extra_files — только если нужны доп. файлы, иначе пустой объект.
|
||
'''
|