Spaces:
Runtime error
Runtime error
| import os | |
| import textwrap | |
| import requests | |
| import gradio as gr | |
| import litellm | |
| from smolagents import CodeAgent, LiteLLMModel, tool, LogLevel | |
| MODEL_NAME = os.getenv("MODEL_NAME", "danielsheep/Qwen3-Coder-30B-A3B-Instruct-1M-Unsloth:UD-IQ3_XXS") | |
| OLLAMA_BASE = os.getenv("OLLAMA_URL", "http://127.0.0.1:11434") | |
| def make_model(): | |
| return LiteLLMModel( | |
| model_id=f"ollama_chat/{MODEL_NAME}", | |
| api_base=OLLAMA_BASE, | |
| num_ctx=16384, | |
| temperature=0.2, | |
| max_tokens=4096, | |
| ) | |
| def web_search(query: str) -> str: | |
| """Ищет краткую информацию в интернете по текстовому запросу. | |
| Args: | |
| query: Текстовый поисковый запрос. | |
| """ | |
| resp = requests.get( | |
| "https://duckduckgo.com/html/", | |
| params={"q": query}, | |
| timeout=8, | |
| headers={"User-Agent": "qwen-agent-demo"}, | |
| ) | |
| resp.raise_for_status() | |
| text = resp.text | |
| return text[:4000] | |
| def run_multi_agent(spec: str, level: str, allow_internet: bool): | |
| model = make_model() | |
| # какие инструменты доступны агентам | |
| tools = [web_search] if allow_internet else [] | |
| # куски промптов, зависящие от allow_internet | |
| if allow_internet: | |
| web_hint_planner = """ | |
| В сгенерированном коде НЕ используй import requests и другие сетевые библиотеки. | |
| Для доступа в интернет у тебя есть ТОЛЬКО инструмент web_search(query: str). | |
| По фичам авторизации/безопасности ОБЯЗАТЕЛЬНО: | |
| - хотя бы один раз вызвать web_search с запросом вроде | |
| "mobile app login test cases", "best practices login screen testing"; | |
| - использовать найденную информацию, чтобы дополнить риски и сценарии. | |
| """ | |
| web_hint_tester = """ | |
| В сгенерированном коде НЕ используй import requests. | |
| Для доступа в интернет у тебя есть ТОЛЬКО инструмент web_search(query: str). | |
| Если фича связана с авторизацией/безопасностью: | |
| - вызови web_search минимум один раз, чтобы найти типичные позитивные, | |
| негативные и граничные кейсы; | |
| - добавь их в набор тестов. | |
| """ | |
| web_hint_reviewer = """ | |
| В сгенерированном коде НЕ используй import requests. | |
| Для доступа в интернет у тебя есть ТОЛЬКО инструмент web_search(query: str). | |
| Можешь использовать его, чтобы уточнить формулировки нефункциональных | |
| требований (безопасность, производительность, устойчивость) и включить | |
| их в отчёт. Не вставляй сырой HTML. | |
| """ | |
| else: | |
| web_hint_common = """ | |
| Интернет недоступен, инструмент web_search отсутствует. | |
| Не пытайся импортировать requests или вызывать web_search в коде. | |
| Опирайся только на своё знание модели. | |
| """ | |
| web_hint_planner = web_hint_common | |
| web_hint_tester = web_hint_common | |
| web_hint_reviewer = web_hint_common | |
| # Агент 1: планировщик | |
| planner = CodeAgent( | |
| tools=tools, | |
| model=model, | |
| add_base_tools=False, | |
| max_steps=5, | |
| ) | |
| plan_prompt = f""" | |
| Ты агент Planner. | |
| Всегда отвечай на РУССКОМ языке. | |
| {web_hint_planner} | |
| По описанию фичи составь план тестирования и список предположений. | |
| Структура результата (внутренняя, для следующих агентов): | |
| - Предположения (список) | |
| - Риски (список) | |
| - Декомпозиция сценариев (список коротких сценариев). | |
| Уровень детализации: {level}. | |
| Описание фичи: | |
| {spec} | |
| """ | |
| plan = planner.run(textwrap.dedent(plan_prompt)) | |
| # Агент 2: генератор тестов | |
| tester = CodeAgent( | |
| tools=tools, | |
| model=model, | |
| add_base_tools=False, | |
| max_steps=5, | |
| ) | |
| tests_prompt = f""" | |
| Ты агент Tester. | |
| Всегда отвечай на РУССКОМ языке. | |
| {web_hint_tester} | |
| На основе плана Planner сгенерируй НАБОР ТЕСТОВ как ПИТОНОВСКИЙ СПИСОК словарей. | |
| Каждый тест = словарь с полями: | |
| - "type" (positive / negative / boundary / nonfunctional), | |
| - "name", | |
| - "short_description". | |
| Не нужно писать текст отчёта, только структуру данных. | |
| Уровень детализации: {level}. | |
| План Planner (для анализа): | |
| {plan} | |
| """ | |
| tests = tester.run(textwrap.dedent(tests_prompt)) | |
| # Агент 3: ревьюер — формирует финальный Markdown | |
| reviewer = CodeAgent( | |
| tools=tools, | |
| model=model, | |
| add_base_tools=False, | |
| max_steps=3, # Достаточно шагов для исправления ошибок, но промпт явно указывает на финальный ответ | |
| ) | |
| reviewer_prompt = f""" | |
| Ты агент Reviewer. | |
| Всегда отвечай на РУССКОМ языке. | |
| {web_hint_reviewer} | |
| Твоя задача — СФОРМИРОВАТЬ ГОТОВЫЙ ОТЧЁТ ДЛЯ ЧЕЛОВЕКА в формате Markdown. | |
| КРИТИЧЕСКИ ВАЖНО: Это ФИНАЛЬНЫЙ шаг. Ты должен сгенерировать Python-код, который создаёт переменную report с Markdown-отчётом и возвращает её. После выполнения этого кода задача будет завершена. | |
| Формат кода должен быть: | |
| report = \"\"\" | |
| ## Предположения | |
| - ... | |
| ## Чеклист | |
| - ... | |
| ## Тесты | |
| ### Позитивные | |
| - Название: ... – краткое описание | |
| ### Негативные | |
| - ... | |
| ### Граничные | |
| - ... | |
| ### Нефункциональные | |
| - ... | |
| \"\"\" | |
| report | |
| Используй следующую структуру отчёта: | |
| ## Предположения | |
| - ... | |
| ## Чеклист | |
| - ... (краткий чеклист для ручного прогона) | |
| ## Тесты | |
| ### Позитивные | |
| - Название: ... – краткое описание | |
| ### Негативные | |
| - ... | |
| ### Граничные | |
| - ... | |
| ### Нефункциональные | |
| - ... | |
| Важные требования: | |
| - Сгенерируй Python-код с переменной report, содержащей Markdown-текст. | |
| - Используй тройные кавычки \"\"\" для многострочной строки. | |
| - В конце кода должна быть переменная report (без print, без return - просто report как последнее выражение). | |
| - Если код выполнился успешно и переменная report создана - это ФИНАЛЬНЫЙ ответ, больше ничего делать не нужно. | |
| - Если в коде была ошибка - исправь её и попробуй снова, но после успешного выполнения сразу остановись. | |
| - CodeAgent вернёт значение переменной report как финальный результат. | |
| - Все заголовки и описания на русском языке. | |
| - Используй информацию из Planner и Tester. | |
| - НЕ используй структуры dict/list в Markdown, только читабельный текст. | |
| - НЕ вызывай инструменты после создания report - это финальный шаг. | |
| - Используй final_answer() после получения достаточного результата | |
| План Planner: | |
| {plan} | |
| Структура тестов Tester: | |
| {tests} | |
| """ | |
| result = reviewer.run(textwrap.dedent(reviewer_prompt)) | |
| # CodeAgent возвращает результат последнего выражения в коде | |
| final_markdown = str(result).strip() if result is not None else "" | |
| return final_markdown | |
| with gr.Blocks(title="Multi-agent QA Assistant (Ollama + Qwen3)") as demo: | |
| gr.Markdown("# Multi-agent QA Assistant (Ollama + Qwen3)") | |
| gr.Markdown( | |
| f"- Model: `{MODEL_NAME}`\n" | |
| f"- Ollama: `{OLLAMA_BASE}`" | |
| ) | |
| spec = gr.Textbox( | |
| label="Описание фичи / сценария", | |
| lines=8, | |
| placeholder="Например: экран авторизации по номеру телефона с маской, автозаполнением, проверкой зарегистрированности..." | |
| ) | |
| level = gr.Dropdown( | |
| ["низкий", "средний", "высокий"], | |
| value="средний", | |
| label="Детализация" | |
| ) | |
| allow_internet = gr.Checkbox( | |
| label="Разрешить агенту выход в интернет (web_search)", | |
| value=False, | |
| ) | |
| run_btn = gr.Button("Запустить мультиагента") | |
| output = gr.Markdown() | |
| run_btn.click( | |
| fn=run_multi_agent, | |
| inputs=[spec, level, allow_internet], | |
| outputs=[output], | |
| ) | |
| def main(): | |
| demo.launch() | |
| if __name__ == "__main__": | |
| main() | |