129 lines
4.3 KiB
Python
129 lines
4.3 KiB
Python
"""Deep research agent: веб-поиск, виртуальные файлы, экспорт на диск.
|
||
|
||
Основано на курсе deep agents from scratch:
|
||
https://github.com/langchain-ai/deep-agents-from-scratch/blob/main/notebooks/4_full_agent.ipynb
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
from dotenv import load_dotenv
|
||
from deepagents import create_deep_agent
|
||
from deepagents.backends import LocalShellBackend
|
||
from langchain_openai import ChatOpenAI
|
||
|
||
from export_tools import DEFAULT_OUTPUT_DIR, WORKSPACE_DIR, export_virtual_files, export_workspace_to_disk
|
||
from web_tools import get_page_content, web_search
|
||
|
||
load_dotenv()
|
||
|
||
BROJS_INFERENCE_URL = "https://platform.brojs.ru/jrnl-bh/api/inference/v1"
|
||
DEFAULT_MODEL = "openai/gpt-oss-20b:free"
|
||
|
||
SYSTEM_PROMPT = """\
|
||
Ты исследовательский deep-агент (стиль deep agents from scratch).
|
||
|
||
Твоя работа:
|
||
1. Искать информацию в интернете через web_search и при необходимости get_page_content.
|
||
2. Сохранять результаты во ВИРТУАЛЬНЫЕ файлы через write_file (например /research/query.md,
|
||
/research/summary.md, /research/sources.txt). Не оставляй всё только в чате.
|
||
3. Используй write_todos для плана исследования.
|
||
4. В КОНЦЕ обязательно вызови export_virtual_files с target_dir="./output",
|
||
чтобы выгрузить виртуальные файлы в реальную файловую систему.
|
||
|
||
Правила:
|
||
- Один инструмент за шаг, если возможно.
|
||
- В файлах — структурированные заметки: запрос, источники, выводы.
|
||
- Отвечай на русском.
|
||
"""
|
||
|
||
|
||
def _api_key() -> str:
|
||
return (
|
||
os.getenv("OPENAI_API_KEY")
|
||
or os.getenv("JOURNAL_MCP_PAT")
|
||
or os.getenv("JOURNAL_TOKEN")
|
||
or ""
|
||
)
|
||
|
||
|
||
def _base_url() -> str:
|
||
if os.getenv("OPENAI_BASE_URL"):
|
||
return os.environ["OPENAI_BASE_URL"]
|
||
if os.getenv("OPENAI_API_KEY"):
|
||
return os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1")
|
||
return BROJS_INFERENCE_URL
|
||
|
||
|
||
def _model() -> str:
|
||
return os.getenv("OPENAI_MODEL") or os.getenv("OPENROUTER_MODEL") or DEFAULT_MODEL
|
||
|
||
|
||
def build_llm() -> ChatOpenAI:
|
||
return ChatOpenAI(
|
||
model=_model(),
|
||
base_url=_base_url(),
|
||
api_key=_api_key(),
|
||
temperature=0.2,
|
||
)
|
||
|
||
|
||
def build_agent():
|
||
WORKSPACE_DIR.mkdir(parents=True, exist_ok=True)
|
||
backend = LocalShellBackend(
|
||
root_dir=str(WORKSPACE_DIR.resolve()),
|
||
virtual_mode=True,
|
||
inherit_env=True,
|
||
)
|
||
llm = build_llm()
|
||
return create_deep_agent(
|
||
model=llm,
|
||
tools=[web_search, get_page_content, export_virtual_files],
|
||
system_prompt=SYSTEM_PROMPT,
|
||
backend=backend,
|
||
)
|
||
|
||
|
||
def run_research(query: str) -> None:
|
||
agent = build_agent()
|
||
print(f"Запрос: {query}\n")
|
||
print("Агент работает (поиск → виртуальные файлы → экспорт)...\n")
|
||
|
||
result = agent.invoke(
|
||
{
|
||
"messages": [
|
||
{
|
||
"role": "human",
|
||
"content": (
|
||
f"Исследуй тему: {query}. "
|
||
"Сохрани заметки в виртуальные файлы и в конце выгрузи их через export_virtual_files."
|
||
),
|
||
}
|
||
]
|
||
}
|
||
)
|
||
|
||
last = result["messages"][-1]
|
||
print("\n--- Ответ агента ---")
|
||
print(getattr(last, "content", last))
|
||
|
||
# Гарантированный экспорт, даже если агент не вызвал инструмент
|
||
exported = export_workspace_to_disk(output_dir=DEFAULT_OUTPUT_DIR)
|
||
print(f"\n--- Файлы на диске ({DEFAULT_OUTPUT_DIR}) ---")
|
||
if exported:
|
||
for path in exported:
|
||
print(f" {path}")
|
||
else:
|
||
print(" (пусто — агент не создал файлов в agent_workspace)")
|
||
|
||
|
||
def main() -> None:
|
||
query = " ".join(sys.argv[1:]).strip() or "LangGraph deep agents и context engineering"
|
||
run_research(query)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|