improve: better prompts and rate-limit retry in pipeline
prompts.py: - Added detailed technical patterns for deepagents, FastMCP, LangGraph, HumanInTheLoop, RAG with Qdrant, stream mode, text game - LLM always via OpenRouter (never hub.pull/Ollama/hardcode) - FastMCP correct pattern (module-level, NOT inside class) - create_agent not compatible with AgentExecutor - documented - DuckDuckGo search pattern (no API key needed) pipeline.py: - Added _invoke_with_retry: auto-retry on 429 rate limit (up to 5x, 90s backoff) - Added TASK_PAUSE (15s) between tasks to reduce rate limit pressure - Progress logging: per-task status messages - Imported asyncio and re Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,7 @@ Gitea owner = "glevelll"
|
||||
[2] Составь письменный план:
|
||||
- какие файлы нужны (main.py, requirements.txt, etc.)
|
||||
- что реализовать в каждом файле
|
||||
- какой технический стек использовать (см. раздел ТЕХНИЧЕСКИЕ ПАТТЕРНЫ ниже)
|
||||
|
||||
[3] gitea_create_repo({"name": "task-<id>", "private": false})
|
||||
→ Создай репозиторий
|
||||
@@ -131,6 +132,470 @@ Gitea owner = "glevelll"
|
||||
- langchain<=1.0.0 в requirements.txt
|
||||
- Пропускать task_update_answer перед task_submit
|
||||
- Писать код только в requirements.txt без main.py
|
||||
|
||||
## ═══════════════════════════════════════════════
|
||||
## ТЕХНИЧЕСКИЕ ПАТТЕРНЫ (читай ПЕРЕД написанием кода)
|
||||
## ═══════════════════════════════════════════════
|
||||
|
||||
### LLM — ВСЕГДА используй OpenRouter (не Ollama, не hub.pull, не hardcode)
|
||||
```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,
|
||||
)
|
||||
```
|
||||
requirements.txt: langchain-openai>=0.3.0
|
||||
|
||||
---
|
||||
|
||||
### deepagents — правильный паттерн (задания про "deep agent", "deepagent", "deep agents from scratch")
|
||||
```python
|
||||
import os, asyncio
|
||||
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"))
|
||||
|
||||
# Виртуальная ФС + реальная shell среда
|
||||
backend = CompositeBackend([
|
||||
LocalShellBackend(workspace_dir="./workspace"),
|
||||
FilesystemBackend(),
|
||||
])
|
||||
|
||||
@tool
|
||||
def web_search(query: str) -> str:
|
||||
"""Search the web for information."""
|
||||
try:
|
||||
from duckduckgo_search import DDGS
|
||||
with DDGS() as ddgs:
|
||||
results = list(ddgs.text(query, max_results=5))
|
||||
return "\\n".join(f"{r['title']}: {r['body']}" for r in results)
|
||||
except Exception as e:
|
||||
return f"Search error: {e}"
|
||||
|
||||
agent = create_deep_agent(
|
||||
llm=llm,
|
||||
tools=[web_search],
|
||||
backend=backend,
|
||||
system_prompt="You are a helpful research agent.",
|
||||
)
|
||||
|
||||
async def main():
|
||||
result = await agent.ainvoke(
|
||||
{"messages": [HumanMessage(content="Search for Python best practices and save to results.txt")]},
|
||||
{"configurable": {"thread_id": "session-1"}},
|
||||
)
|
||||
print(result["messages"][-1].content)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
requirements.txt: deepagents, langchain-openai>=0.3.0, duckduckgo-search
|
||||
|
||||
---
|
||||
|
||||
### FastMCP сервер — ТОЛЬКО на уровне модуля, НИКОГДА внутри класса
|
||||
```python
|
||||
# ПРАВИЛЬНО:
|
||||
from fastmcp import FastMCP
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
mcp = FastMCP("memory-server")
|
||||
STORAGE = Path("memory.json")
|
||||
|
||||
def _load():
|
||||
return json.loads(STORAGE.read_text()) if STORAGE.exists() else {}
|
||||
|
||||
def _save(data):
|
||||
STORAGE.write_text(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
|
||||
@mcp.tool()
|
||||
def save(key: str, value: str) -> bool:
|
||||
"""Save a value by key."""
|
||||
data = _load(); data[key] = value; _save(data)
|
||||
return True
|
||||
|
||||
@mcp.tool()
|
||||
def get(key: str) -> str:
|
||||
"""Get a value by key."""
|
||||
return _load().get(key, "")
|
||||
|
||||
@mcp.tool()
|
||||
def delete(key: str) -> bool:
|
||||
"""Delete a key."""
|
||||
data = _load()
|
||||
if key in data:
|
||||
del data[key]; _save(data); return True
|
||||
return False
|
||||
|
||||
@mcp.tool()
|
||||
def list_keys() -> list:
|
||||
"""List all keys."""
|
||||
return list(_load().keys())
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="stdio")
|
||||
|
||||
# ЗАПРЕЩЕНО — так не работает:
|
||||
# class MemoryServer:
|
||||
# @self.mcp.tool() ← NameError: self не существует в теле класса
|
||||
# def save(self, ...): ...
|
||||
```
|
||||
requirements.txt: fastmcp>=0.1.0, pydantic>=2.0
|
||||
|
||||
---
|
||||
|
||||
### LangChain create_agent — НЕ совместим с AgentExecutor
|
||||
```python
|
||||
import asyncio, os
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain.agents import create_agent
|
||||
from langchain.tools import tool
|
||||
|
||||
llm = ChatOpenAI(model="openai/gpt-oss-20b:free",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
api_key=os.getenv("OPENAI_API_KEY"))
|
||||
|
||||
@tool
|
||||
def my_tool(query: str) -> str:
|
||||
"""Tool description."""
|
||||
return f"result for {query}"
|
||||
|
||||
agent = create_agent(
|
||||
llm=llm,
|
||||
tools=[my_tool],
|
||||
system_prompt="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
async def main():
|
||||
result = await agent.ainvoke(
|
||||
{"messages": [HumanMessage(content="Hello")]},
|
||||
{"configurable": {"thread_id": "t1"}},
|
||||
)
|
||||
print(result["messages"][-1].content)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
# ЗАПРЕЩЕНО:
|
||||
# AgentExecutor(agent=create_agent(...), ...) ← несовместимо!
|
||||
# agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION ← не параметр create_agent
|
||||
```
|
||||
requirements.txt: langchain>=1.2.10, langchain-openai>=0.3.0, langgraph>=0.2.0
|
||||
|
||||
---
|
||||
|
||||
### Human-in-the-Loop через HumanInTheLoopMiddleware
|
||||
```python
|
||||
import asyncio, json, os
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain.agents import create_agent
|
||||
from langchain.agents.middleware import HumanInTheLoopMiddleware
|
||||
from langchain.tools import tool
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.types import Command
|
||||
|
||||
llm = ChatOpenAI(model="openai/gpt-oss-20b:free",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
api_key=os.getenv("OPENAI_API_KEY"))
|
||||
|
||||
@tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get weather for a city."""
|
||||
return f"Sunny, 22C in {city}"
|
||||
|
||||
memory = MemorySaver()
|
||||
agent = create_agent(
|
||||
llm=llm,
|
||||
tools=[get_weather],
|
||||
system_prompt="You are a helpful assistant.",
|
||||
middleware=[HumanInTheLoopMiddleware(interrupt_on={"get_weather": True})],
|
||||
checkpointer=memory,
|
||||
)
|
||||
|
||||
def ask_human(interrupt_value):
|
||||
decisions = []
|
||||
for action in interrupt_value.get("action_requests", []):
|
||||
print(f"Tool: {action['name']}, Args: {action['args']}")
|
||||
ans = input("Approve? (y/n): ").strip().lower()
|
||||
decisions.append({"type": "approve" if ans == "y" else "reject"})
|
||||
return decisions
|
||||
|
||||
async def main():
|
||||
config = {"configurable": {"thread_id": "session-1"}}
|
||||
result = await agent.ainvoke(
|
||||
{"messages": [HumanMessage(content="What's the weather in Moscow?")]},
|
||||
config,
|
||||
)
|
||||
while "__interrupt__" in result:
|
||||
decisions = ask_human(result["__interrupt__"][0].value)
|
||||
result = await agent.ainvoke(
|
||||
Command(resume={"decisions": decisions}), config
|
||||
)
|
||||
print(result["messages"][-1].content)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
requirements.txt: langchain>=1.2.10, langchain-openai>=0.3.0, langgraph>=0.2.0
|
||||
|
||||
---
|
||||
|
||||
### LangGraph interrupt (Human-in-the-loop без middleware)
|
||||
```python
|
||||
import asyncio, os
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain.agents import create_agent
|
||||
from langchain.tools import tool
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.types import Command
|
||||
|
||||
llm = ChatOpenAI(model="openai/gpt-oss-20b:free",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
api_key=os.getenv("OPENAI_API_KEY"))
|
||||
|
||||
@tool
|
||||
def dangerous_action(cmd: str) -> str:
|
||||
"""Execute a dangerous action."""
|
||||
return f"Executed: {cmd}"
|
||||
|
||||
memory = MemorySaver()
|
||||
agent = create_agent(llm=llm, tools=[dangerous_action],
|
||||
checkpointer=memory, interrupt_before=["tools"])
|
||||
|
||||
async def main():
|
||||
config = {"configurable": {"thread_id": "t1"}}
|
||||
result = await agent.ainvoke(
|
||||
{"messages": [HumanMessage(content="Run ls -la")]}, config
|
||||
)
|
||||
# Агент остановился перед вызовом инструмента
|
||||
snapshot = await agent.aget_state(config)
|
||||
if snapshot.next:
|
||||
ans = input(f"Approve tool call? (y/n): ").strip()
|
||||
if ans == "y":
|
||||
result = await agent.ainvoke(Command(resume=None), config)
|
||||
else:
|
||||
result = await agent.ainvoke(
|
||||
Command(resume=None, update={"messages": [
|
||||
HumanMessage(content="User rejected the action.")
|
||||
]}), config
|
||||
)
|
||||
print(result["messages"][-1].content)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### RAG-агент с Qdrant (используй OpenRouter для LLM, Qdrant для векторов)
|
||||
```python
|
||||
import os, asyncio
|
||||
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
|
||||
from langchain_qdrant import QdrantVectorStore
|
||||
from langchain.tools import tool
|
||||
from langchain.agents import create_agent
|
||||
from langchain_core.messages import HumanMessage
|
||||
from qdrant_client import QdrantClient
|
||||
from qdrant_client.models import Distance, VectorParams
|
||||
|
||||
llm = ChatOpenAI(model="openai/gpt-oss-20b:free",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
api_key=os.getenv("OPENAI_API_KEY"))
|
||||
|
||||
# Embeddings через OpenAI-совместимый API (OpenRouter)
|
||||
embeddings = OpenAIEmbeddings(
|
||||
model="text-embedding-3-small",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
)
|
||||
|
||||
# Qdrant in-memory (не требует отдельного сервера)
|
||||
client = QdrantClient(":memory:")
|
||||
client.create_collection("knowledge",
|
||||
vectors_config=VectorParams(size=1536, distance=Distance.COSINE))
|
||||
vector_store = QdrantVectorStore(client=client, collection_name="knowledge",
|
||||
embedding=embeddings)
|
||||
|
||||
@tool
|
||||
def search_knowledge_base(query: str, max_results: int = 5) -> str:
|
||||
"""Semantic search in the knowledge base."""
|
||||
docs = vector_store.similarity_search(query, k=max_results)
|
||||
if not docs:
|
||||
return "No relevant documents found."
|
||||
return "\\n\\n".join(f"{i+1}. {d.page_content}" for i, d in enumerate(docs))
|
||||
|
||||
@tool
|
||||
def add_to_knowledge_base(content: str, title: str = "document") -> str:
|
||||
"""Add text to the knowledge base."""
|
||||
from langchain_core.documents import Document
|
||||
vector_store.add_documents([Document(page_content=content,
|
||||
metadata={"title": title})])
|
||||
return f"Added '{title}' to knowledge base."
|
||||
|
||||
agent = create_agent(
|
||||
llm=llm,
|
||||
tools=[search_knowledge_base, add_to_knowledge_base],
|
||||
system_prompt="You are an assistant with access to a knowledge base.",
|
||||
)
|
||||
|
||||
async def main():
|
||||
await add_to_knowledge_base.ainvoke({"content": "Python is a high-level language.", "title": "python-intro"})
|
||||
result = await agent.ainvoke(
|
||||
{"messages": [HumanMessage(content="What do you know about Python?")]},
|
||||
{"configurable": {"thread_id": "rag-1"}},
|
||||
)
|
||||
print(result["messages"][-1].content)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
requirements.txt: langchain>=1.2.10, langchain-openai>=0.3.0, langgraph>=0.2.0,
|
||||
langchain-qdrant, qdrant-client
|
||||
|
||||
---
|
||||
|
||||
### Stream-режим агента
|
||||
```python
|
||||
import asyncio, os
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain.agents import create_agent
|
||||
from langchain.tools import tool
|
||||
|
||||
llm = ChatOpenAI(model="openai/gpt-oss-20b:free",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
api_key=os.getenv("OPENAI_API_KEY"), streaming=True)
|
||||
|
||||
@tool
|
||||
def calculator(expression: str) -> str:
|
||||
"""Evaluate a math expression."""
|
||||
try:
|
||||
return str(eval(expression, {"__builtins__": {}}, {}))
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
agent = create_agent(llm=llm, tools=[calculator],
|
||||
system_prompt="You are a helpful assistant.")
|
||||
|
||||
async def main():
|
||||
config = {"configurable": {"thread_id": "stream-1"}}
|
||||
# stream_mode="messages" — получаем токены по одному
|
||||
async for event in agent.astream(
|
||||
{"messages": [HumanMessage(content="What is 2+2?")]},
|
||||
config,
|
||||
stream_mode="messages",
|
||||
):
|
||||
if isinstance(event, tuple):
|
||||
msg, metadata = event
|
||||
if hasattr(msg, "content") and msg.content:
|
||||
print(msg.content, end="", flush=True)
|
||||
print()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Задания типа "план / документ" (не чистый кодинг — например ai-fluency)
|
||||
Если задание просит написать план, документ или пройти курс:
|
||||
- Создай main.py который ВЫВОДИТ план в консоль
|
||||
- План должен быть содержательным (минимум 300 слов), структурированным
|
||||
- Имитируй личный опыт: "я понял, что...", "мой план включает..."
|
||||
- Опирайся на тему курса из описания задания
|
||||
|
||||
---
|
||||
|
||||
### Web search без API-ключа (для поисковых агентов)
|
||||
```python
|
||||
from duckduckgo_search import DDGS
|
||||
|
||||
def web_search(query: str) -> str:
|
||||
with DDGS() as ddgs:
|
||||
results = list(ddgs.text(query, max_results=5))
|
||||
return "\\n".join(f"[{r['title']}] {r['body']} ({r['href']})" for r in results)
|
||||
```
|
||||
requirements.txt: duckduckgo-search
|
||||
|
||||
---
|
||||
|
||||
### LangGraph текстовая игра с interrupt
|
||||
```python
|
||||
import asyncio, os
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.types import interrupt, Command
|
||||
from typing import TypedDict, Annotated
|
||||
from langgraph.graph.message import add_messages
|
||||
|
||||
class GameState(TypedDict):
|
||||
messages: Annotated[list, add_messages]
|
||||
location: str
|
||||
inventory: list
|
||||
|
||||
llm = ChatOpenAI(model="openai/gpt-oss-20b:free",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
api_key=os.getenv("OPENAI_API_KEY"))
|
||||
|
||||
def game_master(state: GameState) -> dict:
|
||||
system = SystemMessage(content=(
|
||||
"You are a text adventure game master. "
|
||||
f"Player is at: {state.get('location','start')}. "
|
||||
f"Inventory: {state.get('inventory',[])}. "
|
||||
"Describe what happens and list 2-3 options."
|
||||
))
|
||||
response = llm.invoke([system] + state["messages"])
|
||||
return {"messages": [response]}
|
||||
|
||||
def player_turn(state: GameState) -> Command:
|
||||
player_input = interrupt("Your action: ")
|
||||
return Command(goto="game_master",
|
||||
update={"messages": [HumanMessage(content=player_input)]})
|
||||
|
||||
memory = MemorySaver()
|
||||
builder = StateGraph(GameState)
|
||||
builder.add_node("game_master", game_master)
|
||||
builder.add_node("player_turn", player_turn)
|
||||
builder.add_edge(START, "game_master")
|
||||
builder.add_edge("game_master", "player_turn")
|
||||
game = builder.compile(checkpointer=memory)
|
||||
|
||||
async def main():
|
||||
config = {"configurable": {"thread_id": "game-1"}}
|
||||
state = {"messages": [HumanMessage(content="Start the adventure!")],
|
||||
"location": "forest entrance", "inventory": []}
|
||||
result = await game.ainvoke(state, config)
|
||||
while True:
|
||||
last = result["messages"][-1].content
|
||||
print(f"\\nGame: {last}")
|
||||
if "__interrupt__" in result:
|
||||
action = input("\\nYour action: ").strip()
|
||||
if action.lower() in ("quit", "exit"):
|
||||
break
|
||||
result = await game.ainvoke(Command(resume=action), config)
|
||||
else:
|
||||
break
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
"""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user