Docs update
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -23,3 +23,8 @@ CLAUDE.md
|
||||
GEMINI.md
|
||||
QWEN.md
|
||||
.serena/
|
||||
|
||||
# Others
|
||||
.DS_Store
|
||||
.vscode/
|
||||
*.pdf
|
||||
@@ -219,9 +219,9 @@ graph LR
|
||||
|
||||
| Backend | 저장 위치 | 수명 | files_update 반환 | 사용 시나리오 |
|
||||
|---------|----------|------|------------------|--------------|
|
||||
| **StateBackend** | LangGraph State | 대화 스레드 | ✅ (상태 업데이트용) | 임시 작업 파일 |
|
||||
| **FilesystemBackend** | 로컬 디스크 | 영구 | ❌ (이미 저장됨) | 실제 파일 조작 |
|
||||
| **StoreBackend** | LangGraph Store | 스레드 간 영구 | ❌ (이미 저장됨) | 장기 메모리 |
|
||||
| **StateBackend** | LangGraph State | 대화 스레드 | (상태 업데이트용) | 임시 작업 파일 |
|
||||
| **FilesystemBackend** | 로컬 디스크 | 영구 | (이미 저장됨) | 실제 파일 조작 |
|
||||
| **StoreBackend** | LangGraph Store | 스레드 간 영구 | (이미 저장됨) | 장기 메모리 |
|
||||
| **CompositeBackend** | 라우팅 | 백엔드별 상이 | 백엔드별 상이 | 하이브리드 |
|
||||
|
||||
### 3.3 StateBackend 상세
|
||||
@@ -278,7 +278,7 @@ class FilesystemBackend(BackendProtocol):
|
||||
- Path traversal 차단: `..`, `~` 패턴 거부
|
||||
|
||||
**검색 최적화**:
|
||||
- 1차: Ripgrep (`rg`) JSON 출력 사용
|
||||
- 1차: ripgrep (`rg`) JSON 출력 사용
|
||||
- 2차: Python 폴백 (Ripgrep 미설치 시)
|
||||
|
||||
### 3.5 CompositeBackend 상세
|
||||
|
||||
23
README.md
23
README.md
@@ -1,16 +1,20 @@
|
||||
# DeepAgent Context Engineering
|
||||
|
||||
FileSystem 기반 Context Engineering 을 원활히 수행하는 Multi Agent 구성을 위한 DeepAgent(From LangChain's deepagents library)
|
||||
Agent 2.0 Paradigm 을 잘 구현하는 DeepAgent 를 활용해서, FileSystem 기반 Context Engineering 을 원활히 수행하는 Research 용 Multi Agent 구성(From LangChain's deepagents library)
|
||||
|
||||

|
||||
|
||||
## Agent 1.0 vs Agent 2.0
|
||||
|
||||

|
||||
|
||||
## DeepAgent Technical Guide
|
||||
|
||||
[DeepAgent Technical Guide](./DeepAgents_Technical_Guide.md)
|
||||
|
||||
## DeepAgent - Research
|
||||
## DeepAgent 기반의 Research 수행용 MAS(Multi Agent System)
|
||||
|
||||
### 모듈 구조 요약
|
||||
|
||||
```
|
||||
```bash
|
||||
research_agent/
|
||||
├── agent.py # 메인 오케스트레이터 (create_deep_agent)
|
||||
├── prompts.py # 오케스트레이터 및 Simple SubAgent 프롬프트
|
||||
@@ -34,12 +38,12 @@ research_agent/
|
||||
| 파일 | 역할 |
|
||||
|------|------|
|
||||
| `agent.py` | 메인 에이전트 생성 및 구성 |
|
||||
| `researcher/agent.py` | CompiledSubAgent 패턴의 자율적 연구 에이전트 |
|
||||
| `researcher/prompts.py` | "넓게 탐색 → 깊게 파기" 워크플로우 정의 |
|
||||
| `prompts.py` | 오케스트레이터 워크플로우 및 위임 전략 |
|
||||
| `researcher/agent.py` | 자율적으로 연구하게끔 구성된 에이전트 |
|
||||
| `researcher/prompts.py` | "넓게 탐색 → 깊게 파기" 전략으로 구성된 워크플로우 정의 |
|
||||
| `prompts.py` | 오케스트레이터(Main DeepAgent) 워크플로우 및 위임(Delegation) 전략 |
|
||||
|
||||
|
||||
## DeepAgent UI(made by LangChain)
|
||||
## DeepAgent UI(Made by LangChain)
|
||||
```bash
|
||||
git clone https://github.com/langchain-ai/deep-agents-ui.git
|
||||
cd deep-agents-ui
|
||||
@@ -54,3 +58,4 @@ yarn dev
|
||||
- [LangChain DeepAgent Docs](https://docs.langchain.com/oss/python/deepagents/overview)
|
||||
- [LangGraph CLI Docs](https://docs.langchain.com/langsmith/cli#configuration-file)
|
||||
- [DeepAgent UI](https://github.com/langchain-ai/deep-agents-ui)
|
||||
- [Agents-2.0-deep-agents](https://www.philschmid.de/agents-2.0-deep-agents)
|
||||
|
||||
BIN
agent_20_paradigm.png
Normal file
BIN
agent_20_paradigm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
BIN
agent_versus_10_20.jpeg
Normal file
BIN
agent_versus_10_20.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 304 KiB |
@@ -54,7 +54,7 @@ INSTRUCTIONS = (
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# SubAgent Definitions
|
||||
# SubAgent 정의
|
||||
# =============================================================================
|
||||
|
||||
# 1. Researcher SubAgent: 자율적 연구 DeepAgent (CompiledSubAgent)
|
||||
@@ -125,7 +125,7 @@ def backend_factory(rt: ToolRuntime):
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Deep Agent 생성
|
||||
# DeepAgent 생성
|
||||
# =============================================================================
|
||||
#
|
||||
# 통합 구성:
|
||||
|
||||
@@ -200,7 +200,7 @@ Your role is to coordinate research by delegating tasks from your TODO list to s
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# EXPLORER SubAgent Instructions
|
||||
# EXPLORER SubAgent 지침
|
||||
# =============================================================================
|
||||
|
||||
EXPLORER_INSTRUCTIONS = """You are a fast exploration assistant specialized in quickly finding and analyzing information.
|
||||
@@ -248,7 +248,7 @@ Structure your response as:
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# SYNTHESIZER SubAgent Instructions
|
||||
# SYNTHESIZER SubAgent 지침
|
||||
# =============================================================================
|
||||
|
||||
SYNTHESIZER_INSTRUCTIONS = """You are a synthesis specialist that combines research findings into coherent, well-structured outputs.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"""Autonomous researcher subagent module.
|
||||
"""자율적 연구 SubAgent 모듈.
|
||||
|
||||
This module provides a self-planning, self-reflecting research agent
|
||||
that follows a "breadth-first, depth-second" methodology.
|
||||
이 모듈은 "넓게 탐색 → 깊게 파기" 방법론을 따르는
|
||||
자체 계획 및 자체 반성 연구 에이전트를 제공한다.
|
||||
|
||||
Usage:
|
||||
사용법:
|
||||
from research_agent.researcher import get_researcher_subagent
|
||||
|
||||
researcher = get_researcher_subagent(model=model, backend=backend)
|
||||
# Returns CompiledSubAgent for use in create_deep_agent(subagents=[...])
|
||||
# create_deep_agent(subagents=[...])에 사용할 CompiledSubAgent 반환
|
||||
"""
|
||||
|
||||
from research_agent.researcher.agent import (
|
||||
|
||||
@@ -20,40 +20,40 @@ def create_researcher_agent(
|
||||
model: str | BaseChatModel | None = None,
|
||||
backend: BackendProtocol | BackendFactory | None = None,
|
||||
) -> CompiledStateGraph:
|
||||
"""Create an autonomous researcher DeepAgent.
|
||||
"""자율적 연구 DeepAgent를 생성한다.
|
||||
|
||||
This agent has its own:
|
||||
- Planning loop (write_todos via TodoListMiddleware)
|
||||
- Research loop (tavily_search + think_tool)
|
||||
- Context management (SummarizationMiddleware)
|
||||
- File access (FilesystemMiddleware) for intermediate results
|
||||
이 에이전트는 다음 기능을 자체적으로 보유한다:
|
||||
- 계획 루프 (TodoListMiddleware를 통한 write_todos)
|
||||
- 연구 루프 (tavily_search + think_tool)
|
||||
- 컨텍스트 관리 (SummarizationMiddleware)
|
||||
- 중간 결과 저장을 위한 파일 접근 (FilesystemMiddleware)
|
||||
|
||||
Essentially a "research SubGraph" that operates autonomously.
|
||||
본질적으로 자율적으로 작동하는 "연구 SubGraph"이다.
|
||||
|
||||
Args:
|
||||
model: LLM to use. Defaults to gpt-4.1 with temperature=0.
|
||||
backend: Backend for file operations. If provided,
|
||||
researcher can save intermediate results to filesystem.
|
||||
model: 사용할 LLM. 기본값은 temperature=0인 gpt-4.1.
|
||||
backend: 파일 작업용 백엔드. 제공되면
|
||||
연구자가 중간 결과를 파일시스템에 저장할 수 있다.
|
||||
|
||||
Returns:
|
||||
CompiledStateGraph: A fully autonomous research agent that can be
|
||||
used standalone or as a CompiledSubAgent in an orchestrator.
|
||||
CompiledStateGraph: 독립적으로 사용하거나 오케스트레이터의
|
||||
CompiledSubAgent로 사용할 수 있는 완전 자율적 연구 에이전트.
|
||||
|
||||
Example:
|
||||
# Standalone usage
|
||||
# 독립 사용
|
||||
researcher = create_researcher_agent()
|
||||
result = researcher.invoke({
|
||||
"messages": [HumanMessage("Research quantum computing trends")]
|
||||
"messages": [HumanMessage("양자 컴퓨팅 트렌드 연구")]
|
||||
})
|
||||
|
||||
# As SubAgent in orchestrator
|
||||
# 오케스트레이터의 SubAgent로 사용
|
||||
subagent = get_researcher_subagent()
|
||||
orchestrator = create_deep_agent(subagents=[subagent, ...])
|
||||
"""
|
||||
if model is None:
|
||||
model = ChatOpenAI(model="gpt-4.1", temperature=0.0)
|
||||
|
||||
# Format prompt with current date
|
||||
# 현재 날짜로 프롬프트 포맷팅
|
||||
current_date = datetime.now().strftime("%Y-%m-%d")
|
||||
formatted_prompt = AUTONOMOUS_RESEARCHER_INSTRUCTIONS.format(date=current_date)
|
||||
|
||||
@@ -69,20 +69,20 @@ def get_researcher_subagent(
|
||||
model: str | BaseChatModel | None = None,
|
||||
backend: BackendProtocol | BackendFactory | None = None,
|
||||
) -> dict:
|
||||
"""Get researcher as a CompiledSubAgent for use in orchestrator.
|
||||
"""오케스트레이터에서 사용할 CompiledSubAgent로 연구자를 가져온다.
|
||||
|
||||
This function creates an autonomous researcher agent and wraps it
|
||||
in the CompiledSubAgent format expected by SubAgentMiddleware.
|
||||
이 함수는 자율적 연구 에이전트를 생성하고 SubAgentMiddleware가
|
||||
기대하는 CompiledSubAgent 형식으로 래핑한다.
|
||||
|
||||
Args:
|
||||
model: LLM to use. Defaults to gpt-4.1.
|
||||
backend: Backend for file operations.
|
||||
model: 사용할 LLM. 기본값은 gpt-4.1.
|
||||
backend: 파일 작업용 백엔드.
|
||||
|
||||
Returns:
|
||||
dict: CompiledSubAgent with keys:
|
||||
dict: 다음 키를 가진 CompiledSubAgent:
|
||||
- name: "researcher"
|
||||
- description: Used by orchestrator to decide when to delegate
|
||||
- runnable: The autonomous researcher agent
|
||||
- description: 오케스트레이터가 위임 결정 시 사용
|
||||
- runnable: 자율적 연구 에이전트
|
||||
|
||||
Example:
|
||||
from research_agent.researcher import get_researcher_subagent
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
"""Skills module for research_agent.
|
||||
"""research_agent용 Skills 모듈.
|
||||
|
||||
This module implements the Agent Skills pattern with progressive disclosure:
|
||||
1. At session start, parse YAML frontmatter from SKILL.md files
|
||||
2. Inject skill metadata (name + description) into system prompt
|
||||
3. Agent reads full SKILL.md content when the skill is relevant
|
||||
이 모듈은 점진적 공개(Progressive Disclosure) 패턴으로 Agent Skills 패턴을 구현한다:
|
||||
1. 세션 시작 시 SKILL.md 파일에서 YAML 프론트매터 파싱
|
||||
2. 스킬 메타데이터(이름 + 설명)를 시스템 프롬프트에 주입
|
||||
3. 스킬이 관련될 때 에이전트가 전체 SKILL.md 콘텐츠 읽기
|
||||
|
||||
Public API:
|
||||
- SkillsMiddleware: Middleware to integrate skills into agent execution
|
||||
- list_skills: Load skill metadata from directories
|
||||
- SkillMetadata: TypedDict for skill metadata structure
|
||||
공개 API:
|
||||
- SkillsMiddleware: 에이전트 실행에 스킬을 통합하는 미들웨어
|
||||
- list_skills: 디렉토리에서 스킬 메타데이터 로드
|
||||
- SkillMetadata: 스킬 메타데이터 구조용 TypedDict
|
||||
"""
|
||||
|
||||
from research_agent.skills.load import SkillMetadata, list_skills
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
"""Skill loader for parsing and loading agent skills from SKILL.md files.
|
||||
"""SKILL.md 파일에서 에이전트 스킬을 파싱하고 로드하는 스킬 로더.
|
||||
|
||||
This module implements the Anthropic Agent Skills pattern via YAML frontmatter parsing.
|
||||
Each skill is a directory containing a SKILL.md file with:
|
||||
- YAML frontmatter (name, description required)
|
||||
- Markdown instructions for the agent
|
||||
- Optional supporting files (scripts, configs, etc.)
|
||||
이 모듈은 YAML 프론트매터 파싱을 통해 Anthropic Agent Skills 패턴을 구현한다.
|
||||
각 스킬은 다음을 포함하는 SKILL.md 파일이 있는 디렉토리이다:
|
||||
- YAML 프론트매터 (name, description 필수)
|
||||
- 에이전트용 마크다운 지침
|
||||
- 선택적 지원 파일 (스크립트, 설정 등)
|
||||
|
||||
Example SKILL.md structure:
|
||||
SKILL.md 구조 예시:
|
||||
```markdown
|
||||
---
|
||||
name: web-research
|
||||
description: A structured approach to conducting thorough web research
|
||||
description: 철저한 웹 리서치를 수행하기 위한 구조화된 접근법
|
||||
---
|
||||
|
||||
# Web Research Skill
|
||||
# 웹 리서치 스킬
|
||||
|
||||
## When to Use
|
||||
- When the user requests topic research
|
||||
## 사용 시점
|
||||
- 사용자가 주제 연구를 요청할 때
|
||||
...
|
||||
```
|
||||
|
||||
Adapted from deepagents-cli for use in research_agent project.
|
||||
research_agent 프로젝트용으로 deepagents-cli에서 적응함.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -34,56 +34,56 @@ import yaml
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Maximum file size for SKILL.md files (10MB) - DoS protection
|
||||
# SKILL.md 파일 최대 크기 (10MB) - DoS 방지
|
||||
MAX_SKILL_FILE_SIZE = 10 * 1024 * 1024
|
||||
|
||||
# Agent Skills specification constraints (https://agentskills.io/specification)
|
||||
# Agent Skills 명세 제약 조건 (https://agentskills.io/specification)
|
||||
MAX_SKILL_NAME_LENGTH = 64
|
||||
MAX_SKILL_DESCRIPTION_LENGTH = 1024
|
||||
|
||||
|
||||
class SkillMetadata(TypedDict):
|
||||
"""Skill metadata following Agent Skills specification."""
|
||||
"""Agent Skills 명세를 따르는 스킬 메타데이터."""
|
||||
|
||||
name: str
|
||||
"""Skill name (max 64 chars, lowercase alphanumeric and hyphens)."""
|
||||
"""스킬 이름 (최대 64자, 소문자 영숫자와 하이픈만)."""
|
||||
|
||||
description: str
|
||||
"""Description of what the skill does (max 1024 chars)."""
|
||||
"""스킬이 하는 일에 대한 설명 (최대 1024자)."""
|
||||
|
||||
path: str
|
||||
"""Path to the SKILL.md file."""
|
||||
"""SKILL.md 파일 경로."""
|
||||
|
||||
source: str
|
||||
"""Source of the skill ('user' or 'project')."""
|
||||
"""스킬 출처 ('user' 또는 'project')."""
|
||||
|
||||
# Optional fields per Agent Skills specification
|
||||
# Agent Skills 명세에 따른 선택적 필드
|
||||
license: NotRequired[str | None]
|
||||
"""License name or reference to bundled license file."""
|
||||
"""라이선스 이름 또는 번들된 라이선스 파일 참조."""
|
||||
|
||||
compatibility: NotRequired[str | None]
|
||||
"""Environment requirements (max 500 chars)."""
|
||||
"""환경 요구사항 (최대 500자)."""
|
||||
|
||||
metadata: NotRequired[dict[str, str] | None]
|
||||
"""Arbitrary key-value mapping for additional metadata."""
|
||||
"""추가 메타데이터용 임의 키-값 매핑."""
|
||||
|
||||
allowed_tools: NotRequired[str | None]
|
||||
"""Space-separated list of pre-approved tools."""
|
||||
"""사전 승인된 도구의 공백 구분 목록."""
|
||||
|
||||
|
||||
def _is_safe_path(path: Path, base_dir: Path) -> bool:
|
||||
"""Check if path is safely contained within base_dir.
|
||||
"""경로가 base_dir 내에 안전하게 포함되어 있는지 확인한다.
|
||||
|
||||
Prevents directory traversal attacks via symlinks or path manipulation.
|
||||
Resolves both paths to canonical form (following symlinks) and verifies
|
||||
the target path is within the base directory.
|
||||
심볼릭 링크나 경로 조작을 통한 디렉토리 탐색 공격을 방지한다.
|
||||
두 경로 모두 정규 형식으로 변환(심볼릭 링크 따라감)하고
|
||||
대상 경로가 기본 디렉토리 내에 있는지 확인한다.
|
||||
|
||||
Args:
|
||||
path: Path to validate
|
||||
base_dir: Base directory that should contain the path
|
||||
path: 검증할 경로
|
||||
base_dir: 경로가 포함되어야 하는 기본 디렉토리
|
||||
|
||||
Returns:
|
||||
True if path is safely within base_dir, False otherwise
|
||||
경로가 base_dir 내에 안전하게 있으면 True, 그렇지 않으면 False
|
||||
"""
|
||||
try:
|
||||
resolved_path = path.resolve()
|
||||
@@ -91,113 +91,116 @@ def _is_safe_path(path: Path, base_dir: Path) -> bool:
|
||||
resolved_path.relative_to(resolved_base)
|
||||
return True
|
||||
except ValueError:
|
||||
# Path is not a subdirectory of base_dir
|
||||
# 경로가 base_dir의 하위 디렉토리가 아님
|
||||
return False
|
||||
except (OSError, RuntimeError):
|
||||
# Error resolving path (e.g., circular symlinks)
|
||||
# 경로 해석 오류 (예: 순환 심볼릭 링크)
|
||||
return False
|
||||
|
||||
|
||||
def _validate_skill_name(name: str, directory_name: str) -> tuple[bool, str]:
|
||||
"""Validate skill name per Agent Skills specification.
|
||||
"""Agent Skills 명세에 따라 스킬 이름을 검증한다.
|
||||
|
||||
Requirements:
|
||||
- Max 64 characters
|
||||
- Lowercase alphanumeric and hyphens only (a-z, 0-9, -)
|
||||
- Cannot start or end with hyphen
|
||||
- No consecutive hyphens
|
||||
- Must match parent directory name
|
||||
요구사항:
|
||||
- 최대 64자
|
||||
- 소문자 영숫자와 하이픈만 (a-z, 0-9, -)
|
||||
- 하이픈으로 시작하거나 끝날 수 없음
|
||||
- 연속 하이픈 없음
|
||||
- 부모 디렉토리 이름과 일치해야 함
|
||||
|
||||
Args:
|
||||
name: Skill name from YAML frontmatter
|
||||
directory_name: Parent directory name
|
||||
name: YAML 프론트매터의 스킬 이름
|
||||
directory_name: 부모 디렉토리 이름
|
||||
|
||||
Returns:
|
||||
(is_valid, error_message) tuple. Error message is empty if valid.
|
||||
(is_valid, error_message) 튜플. 유효하면 에러 메시지는 빈 문자열.
|
||||
"""
|
||||
if not name:
|
||||
return False, "Name is required"
|
||||
return False, "이름은 필수입니다"
|
||||
if len(name) > MAX_SKILL_NAME_LENGTH:
|
||||
return False, "Name exceeds 64 characters"
|
||||
# Pattern: lowercase alphanumeric, single hyphens between segments
|
||||
return False, "이름이 64자를 초과합니다"
|
||||
# 패턴: 소문자 영숫자, 세그먼트 사이에 단일 하이픈
|
||||
if not re.match(r"^[a-z0-9]+(-[a-z0-9]+)*$", name):
|
||||
return False, "Name must use only lowercase alphanumeric and single hyphens"
|
||||
return False, "이름은 소문자 영숫자와 단일 하이픈만 사용해야 합니다"
|
||||
if name != directory_name:
|
||||
return False, f"Name '{name}' must match directory name '{directory_name}'"
|
||||
return (
|
||||
False,
|
||||
f"이름 '{name}'은 디렉토리 이름 '{directory_name}'과 일치해야 합니다",
|
||||
)
|
||||
return True, ""
|
||||
|
||||
|
||||
def _parse_skill_metadata(skill_md_path: Path, source: str) -> SkillMetadata | None:
|
||||
"""Parse YAML frontmatter from SKILL.md file per Agent Skills specification.
|
||||
"""Agent Skills 명세에 따라 SKILL.md 파일에서 YAML 프론트매터를 파싱한다.
|
||||
|
||||
Args:
|
||||
skill_md_path: Path to SKILL.md file
|
||||
source: Skill source ('user' or 'project')
|
||||
skill_md_path: SKILL.md 파일 경로
|
||||
source: 스킬 출처 ('user' 또는 'project')
|
||||
|
||||
Returns:
|
||||
SkillMetadata with all fields, or None if parsing fails
|
||||
모든 필드가 있는 SkillMetadata, 파싱 실패 시 None
|
||||
"""
|
||||
try:
|
||||
# Security: Check file size to prevent DoS
|
||||
# 보안: DoS 방지를 위한 파일 크기 확인
|
||||
file_size = skill_md_path.stat().st_size
|
||||
if file_size > MAX_SKILL_FILE_SIZE:
|
||||
logger.warning(
|
||||
"Skipping %s: file too large (%d bytes)", skill_md_path, file_size
|
||||
"%s 건너뜀: 파일이 너무 큼 (%d 바이트)", skill_md_path, file_size
|
||||
)
|
||||
return None
|
||||
|
||||
content = skill_md_path.read_text(encoding="utf-8")
|
||||
|
||||
# Match YAML frontmatter between --- delimiters
|
||||
# --- 구분자 사이의 YAML 프론트매터 매칭
|
||||
frontmatter_pattern = r"^---\s*\n(.*?)\n---\s*\n"
|
||||
match = re.match(frontmatter_pattern, content, re.DOTALL)
|
||||
|
||||
if not match:
|
||||
logger.warning(
|
||||
"Skipping %s: no valid YAML frontmatter found", skill_md_path
|
||||
"%s 건너뜀: 유효한 YAML 프론트매터를 찾을 수 없음", skill_md_path
|
||||
)
|
||||
return None
|
||||
|
||||
frontmatter_str = match.group(1)
|
||||
|
||||
# Parse YAML with safe_load for proper nested structure support
|
||||
# 적절한 중첩 구조 지원을 위해 safe_load로 YAML 파싱
|
||||
try:
|
||||
frontmatter_data = yaml.safe_load(frontmatter_str)
|
||||
except yaml.YAMLError as e:
|
||||
logger.warning("Invalid YAML in %s: %s", skill_md_path, e)
|
||||
logger.warning("%s의 YAML이 유효하지 않음: %s", skill_md_path, e)
|
||||
return None
|
||||
|
||||
if not isinstance(frontmatter_data, dict):
|
||||
logger.warning("Skipping %s: frontmatter is not a mapping", skill_md_path)
|
||||
logger.warning("%s 건너뜀: 프론트매터가 매핑이 아님", skill_md_path)
|
||||
return None
|
||||
|
||||
# Validate required fields
|
||||
# 필수 필드 검증
|
||||
name = frontmatter_data.get("name")
|
||||
description = frontmatter_data.get("description")
|
||||
|
||||
if not name or not description:
|
||||
logger.warning(
|
||||
"Skipping %s: missing required 'name' or 'description'", skill_md_path
|
||||
"%s 건너뜀: 필수 'name' 또는 'description' 누락", skill_md_path
|
||||
)
|
||||
return None
|
||||
|
||||
# Validate name format per spec (warn but load for backward compatibility)
|
||||
# 명세에 따른 이름 형식 검증 (역호환성을 위해 경고하지만 로드)
|
||||
directory_name = skill_md_path.parent.name
|
||||
is_valid, error = _validate_skill_name(str(name), directory_name)
|
||||
if not is_valid:
|
||||
logger.warning(
|
||||
"Skill '%s' in %s does not follow Agent Skills spec: %s. "
|
||||
"Consider renaming for spec compliance.",
|
||||
"'%s' 스킬 (%s)이 Agent Skills 명세를 따르지 않음: %s. "
|
||||
"명세 준수를 위해 이름 변경을 고려하세요.",
|
||||
name,
|
||||
skill_md_path,
|
||||
error,
|
||||
)
|
||||
|
||||
# Validate description length (spec: max 1024 chars)
|
||||
# 설명 길이 검증 (명세: 최대 1024자)
|
||||
description_str = str(description)
|
||||
if len(description_str) > MAX_SKILL_DESCRIPTION_LENGTH:
|
||||
logger.warning(
|
||||
"Description in %s exceeds %d chars, truncating",
|
||||
"%s의 설명이 %d자를 초과하여 잘림",
|
||||
skill_md_path,
|
||||
MAX_SKILL_DESCRIPTION_LENGTH,
|
||||
)
|
||||
@@ -215,35 +218,35 @@ def _parse_skill_metadata(skill_md_path: Path, source: str) -> SkillMetadata | N
|
||||
)
|
||||
|
||||
except (OSError, UnicodeDecodeError) as e:
|
||||
logger.warning("Error reading %s: %s", skill_md_path, e)
|
||||
logger.warning("%s 읽기 오류: %s", skill_md_path, e)
|
||||
return None
|
||||
|
||||
|
||||
def _list_skills_from_dir(skills_dir: Path, source: str) -> list[SkillMetadata]:
|
||||
"""List all skills from a single skills directory (internal helper).
|
||||
"""단일 스킬 디렉토리에서 모든 스킬을 나열한다 (내부 헬퍼).
|
||||
|
||||
Scans the skills directory for subdirectories containing SKILL.md files,
|
||||
parses YAML frontmatter, and returns skill metadata.
|
||||
스킬 디렉토리를 스캔하여 SKILL.md 파일을 포함하는 하위 디렉토리를 찾고,
|
||||
YAML 프론트매터를 파싱하여 스킬 메타데이터를 반환한다.
|
||||
|
||||
Skills organization:
|
||||
스킬 구조:
|
||||
skills/
|
||||
├── skill-name/
|
||||
│ ├── SKILL.md # Required: instructions with YAML frontmatter
|
||||
│ ├── script.py # Optional: supporting files
|
||||
│ └── config.json # Optional: supporting files
|
||||
│ ├── SKILL.md # 필수: YAML 프론트매터가 있는 지침
|
||||
│ ├── script.py # 선택: 지원 파일
|
||||
│ └── config.json # 선택: 지원 파일
|
||||
|
||||
Args:
|
||||
skills_dir: Path to skills directory
|
||||
source: Skill source ('user' or 'project')
|
||||
skills_dir: 스킬 디렉토리 경로
|
||||
source: 스킬 출처 ('user' 또는 'project')
|
||||
|
||||
Returns:
|
||||
List of skill metadata dictionaries with name, description, path, and source
|
||||
name, description, path, source가 있는 스킬 메타데이터 딕셔너리 목록
|
||||
"""
|
||||
skills_dir = skills_dir.expanduser()
|
||||
if not skills_dir.exists():
|
||||
return []
|
||||
|
||||
# Resolve base directory for security checks
|
||||
# 보안 검사를 위한 기본 디렉토리 해석
|
||||
try:
|
||||
resolved_base = skills_dir.resolve()
|
||||
except (OSError, RuntimeError):
|
||||
@@ -251,25 +254,25 @@ def _list_skills_from_dir(skills_dir: Path, source: str) -> list[SkillMetadata]:
|
||||
|
||||
skills: list[SkillMetadata] = []
|
||||
|
||||
# Iterate over subdirectories
|
||||
# 하위 디렉토리 순회
|
||||
for skill_dir in skills_dir.iterdir():
|
||||
# Security: catch symlinks pointing outside skills directory
|
||||
# 보안: 스킬 디렉토리 외부를 가리키는 심볼릭 링크 포착
|
||||
if not _is_safe_path(skill_dir, resolved_base):
|
||||
continue
|
||||
|
||||
if not skill_dir.is_dir():
|
||||
continue
|
||||
|
||||
# Look for SKILL.md file
|
||||
# SKILL.md 파일 찾기
|
||||
skill_md_path = skill_dir / "SKILL.md"
|
||||
if not skill_md_path.exists():
|
||||
continue
|
||||
|
||||
# Security: validate SKILL.md path before reading
|
||||
# 보안: 읽기 전에 SKILL.md 경로 검증
|
||||
if not _is_safe_path(skill_md_path, resolved_base):
|
||||
continue
|
||||
|
||||
# Parse metadata
|
||||
# 메타데이터 파싱
|
||||
metadata = _parse_skill_metadata(skill_md_path, source=source)
|
||||
if metadata:
|
||||
skills.append(metadata)
|
||||
@@ -282,32 +285,32 @@ def list_skills(
|
||||
user_skills_dir: Path | None = None,
|
||||
project_skills_dir: Path | None = None,
|
||||
) -> list[SkillMetadata]:
|
||||
"""List skills from user and/or project directories.
|
||||
"""사용자 및/또는 프로젝트 디렉토리에서 스킬을 나열한다.
|
||||
|
||||
When both directories are provided, project skills with the same name
|
||||
as user skills will override the user skills.
|
||||
두 디렉토리가 모두 제공되면, 사용자 스킬과 동일한 이름의 프로젝트 스킬이
|
||||
사용자 스킬을 오버라이드한다.
|
||||
|
||||
Args:
|
||||
user_skills_dir: Path to user-level skills directory
|
||||
project_skills_dir: Path to project-level skills directory
|
||||
user_skills_dir: 사용자 레벨 스킬 디렉토리 경로
|
||||
project_skills_dir: 프로젝트 레벨 스킬 디렉토리 경로
|
||||
|
||||
Returns:
|
||||
Merged list of skill metadata from both sources, with project skills
|
||||
taking precedence over user skills when names conflict
|
||||
두 출처의 스킬 메타데이터를 병합한 목록.
|
||||
이름이 충돌할 때 프로젝트 스킬이 우선됨
|
||||
"""
|
||||
all_skills: dict[str, SkillMetadata] = {}
|
||||
|
||||
# Load user skills first (baseline)
|
||||
# 사용자 스킬을 먼저 로드 (기본)
|
||||
if user_skills_dir:
|
||||
user_skills = _list_skills_from_dir(user_skills_dir, source="user")
|
||||
for skill in user_skills:
|
||||
all_skills[skill["name"]] = skill
|
||||
|
||||
# Load project skills second (override/extend)
|
||||
# 프로젝트 스킬을 두 번째로 로드 (오버라이드/확장)
|
||||
if project_skills_dir:
|
||||
project_skills = _list_skills_from_dir(project_skills_dir, source="project")
|
||||
for skill in project_skills:
|
||||
# Project skills override user skills with same name
|
||||
# 프로젝트 스킬이 같은 이름의 사용자 스킬을 오버라이드
|
||||
all_skills[skill["name"]] = skill
|
||||
|
||||
return list(all_skills.values())
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
"""Middleware for loading and exposing agent skills to the system prompt.
|
||||
"""에이전트 스킬을 시스템 프롬프트에 로드하고 노출하기 위한 미들웨어.
|
||||
|
||||
This middleware implements Anthropic's "Agent Skills" pattern via progressive disclosure:
|
||||
1. At session start, parse YAML frontmatter from SKILL.md files
|
||||
2. Inject skill metadata (name + description) into system prompt
|
||||
3. Agent reads full SKILL.md content when the skill is relevant
|
||||
이 미들웨어는 점진적 공개를 통해 Anthropic의 "Agent Skills" 패턴을 구현한다:
|
||||
1. 세션 시작 시 SKILL.md 파일에서 YAML 프론트매터 파싱
|
||||
2. 스킬 메타데이터(이름 + 설명)를 시스템 프롬프트에 주입
|
||||
3. 스킬이 관련될 때 에이전트가 전체 SKILL.md 콘텐츠 읽기
|
||||
|
||||
Skills directory structure (project-level):
|
||||
스킬 디렉토리 구조 (프로젝트 레벨):
|
||||
{PROJECT_ROOT}/skills/
|
||||
├── web-research/
|
||||
│ ├── SKILL.md # Required: YAML frontmatter + instructions
|
||||
│ └── helper.py # Optional: supporting files
|
||||
│ ├── SKILL.md # 필수: YAML 프론트매터 + 지침
|
||||
│ └── helper.py # 선택: 지원 파일
|
||||
├── code-review/
|
||||
│ ├── SKILL.md
|
||||
│ └── checklist.md
|
||||
|
||||
Adapted from deepagents-cli for use in research_agent project.
|
||||
research_agent 프로젝트용으로 deepagents-cli에서 적응함.
|
||||
"""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
@@ -33,20 +33,20 @@ from research_agent.skills.load import SkillMetadata, list_skills
|
||||
|
||||
|
||||
class SkillsState(AgentState):
|
||||
"""State for skills middleware."""
|
||||
"""스킬 미들웨어용 상태."""
|
||||
|
||||
skills_metadata: NotRequired[list[SkillMetadata]]
|
||||
"""List of loaded skill metadata (name, description, path)."""
|
||||
"""로드된 스킬 메타데이터 목록 (이름, 설명, 경로)."""
|
||||
|
||||
|
||||
class SkillsStateUpdate(TypedDict):
|
||||
"""State update for skills middleware."""
|
||||
"""스킬 미들웨어용 상태 업데이트."""
|
||||
|
||||
skills_metadata: list[SkillMetadata]
|
||||
"""List of loaded skill metadata (name, description, path)."""
|
||||
"""로드된 스킬 메타데이터 목록 (이름, 설명, 경로)."""
|
||||
|
||||
|
||||
# Skills system documentation template
|
||||
# 스킬 시스템 문서 템플릿
|
||||
SKILLS_SYSTEM_PROMPT = """
|
||||
|
||||
## Skills System
|
||||
@@ -94,21 +94,21 @@ Note: Skills are tools that make you more capable and consistent. When in doubt,
|
||||
|
||||
|
||||
class SkillsMiddleware(AgentMiddleware):
|
||||
"""Middleware for loading and exposing agent skills.
|
||||
"""에이전트 스킬을 로드하고 노출하기 위한 미들웨어.
|
||||
|
||||
This middleware implements Anthropic's Agent Skills pattern:
|
||||
- At session start: load skill metadata (name, description) from YAML frontmatter
|
||||
- Inject skill list into system prompt for discoverability
|
||||
- Agent reads full SKILL.md content when skill is relevant (progressive disclosure)
|
||||
이 미들웨어는 Anthropic의 Agent Skills 패턴을 구현한다:
|
||||
- 세션 시작 시: YAML 프론트매터에서 스킬 메타데이터(이름, 설명) 로드
|
||||
- 발견 가능성을 위해 시스템 프롬프트에 스킬 목록 주입
|
||||
- 스킬이 관련될 때 에이전트가 전체 SKILL.md 콘텐츠 읽기 (점진적 공개)
|
||||
|
||||
Supports both user-level and project-level skills:
|
||||
- Project skills: {PROJECT_ROOT}/skills/
|
||||
- Project skills override user skills with the same name
|
||||
사용자 레벨과 프로젝트 레벨 스킬 모두 지원:
|
||||
- 프로젝트 스킬: {PROJECT_ROOT}/skills/
|
||||
- 프로젝트 스킬이 같은 이름의 사용자 스킬을 오버라이드
|
||||
|
||||
Args:
|
||||
skills_dir: Path to user-level skills directory (agent-specific).
|
||||
assistant_id: Agent identifier for path references in prompt.
|
||||
project_skills_dir: Optional path to project-level skills directory.
|
||||
skills_dir: 사용자 레벨 스킬 디렉토리 경로 (에이전트별).
|
||||
assistant_id: 프롬프트의 경로 참조용 에이전트 식별자.
|
||||
project_skills_dir: 프로젝트 레벨 스킬 디렉토리 경로 (선택).
|
||||
"""
|
||||
|
||||
state_schema = SkillsState
|
||||
@@ -120,24 +120,24 @@ class SkillsMiddleware(AgentMiddleware):
|
||||
assistant_id: str,
|
||||
project_skills_dir: str | Path | None = None,
|
||||
) -> None:
|
||||
"""Initialize skills middleware.
|
||||
"""스킬 미들웨어를 초기화한다.
|
||||
|
||||
Args:
|
||||
skills_dir: Path to user-level skills directory.
|
||||
assistant_id: Agent identifier.
|
||||
project_skills_dir: Optional path to project-level skills directory.
|
||||
skills_dir: 사용자 레벨 스킬 디렉토리 경로.
|
||||
assistant_id: 에이전트 식별자.
|
||||
project_skills_dir: 프로젝트 레벨 스킬 디렉토리 경로 (선택).
|
||||
"""
|
||||
self.skills_dir = Path(skills_dir).expanduser()
|
||||
self.assistant_id = assistant_id
|
||||
self.project_skills_dir = (
|
||||
Path(project_skills_dir).expanduser() if project_skills_dir else None
|
||||
)
|
||||
# Store paths for prompt display
|
||||
# 프롬프트 표시용 경로 저장
|
||||
self.user_skills_display = f"~/.deepagents/{assistant_id}/skills"
|
||||
self.system_prompt_template = SKILLS_SYSTEM_PROMPT
|
||||
|
||||
def _format_skills_locations(self) -> str:
|
||||
"""Format skill locations for system prompt display."""
|
||||
"""시스템 프롬프트 표시용 스킬 위치를 포맷팅한다."""
|
||||
locations = [f"**User Skills**: `{self.user_skills_display}`"]
|
||||
if self.project_skills_dir:
|
||||
locations.append(
|
||||
@@ -146,20 +146,20 @@ class SkillsMiddleware(AgentMiddleware):
|
||||
return "\n".join(locations)
|
||||
|
||||
def _format_skills_list(self, skills: list[SkillMetadata]) -> str:
|
||||
"""Format skill metadata for system prompt display."""
|
||||
"""시스템 프롬프트 표시용 스킬 메타데이터를 포맷팅한다."""
|
||||
if not skills:
|
||||
locations = [f"{self.user_skills_display}/"]
|
||||
if self.project_skills_dir:
|
||||
locations.append(f"{self.project_skills_dir}/")
|
||||
return f"(No skills available. You can create skills in {' or '.join(locations)})"
|
||||
|
||||
# Group skills by source
|
||||
# 출처별로 스킬 그룹화
|
||||
user_skills = [s for s in skills if s["source"] == "user"]
|
||||
project_skills = [s for s in skills if s["source"] == "project"]
|
||||
|
||||
lines = []
|
||||
|
||||
# Display user skills
|
||||
# 사용자 스킬 표시
|
||||
if user_skills:
|
||||
lines.append("**User Skills:**")
|
||||
for skill in user_skills:
|
||||
@@ -167,7 +167,7 @@ class SkillsMiddleware(AgentMiddleware):
|
||||
lines.append(f" → To read full instructions: `{skill['path']}`")
|
||||
lines.append("")
|
||||
|
||||
# Display project skills
|
||||
# 프로젝트 스킬 표시
|
||||
if project_skills:
|
||||
lines.append("**Project Skills:**")
|
||||
for skill in project_skills:
|
||||
@@ -179,19 +179,19 @@ class SkillsMiddleware(AgentMiddleware):
|
||||
def before_agent(
|
||||
self, state: SkillsState, runtime: Runtime
|
||||
) -> SkillsStateUpdate | None:
|
||||
"""Load skill metadata before agent execution.
|
||||
"""에이전트 실행 전에 스킬 메타데이터를 로드한다.
|
||||
|
||||
This runs once at session start to discover available skills from
|
||||
both user-level and project-level directories.
|
||||
세션 시작 시 한 번 실행되어 사용자 레벨과 프로젝트 레벨
|
||||
디렉토리 모두에서 사용 가능한 스킬을 발견한다.
|
||||
|
||||
Args:
|
||||
state: Current agent state.
|
||||
runtime: Runtime context.
|
||||
state: 현재 에이전트 상태.
|
||||
runtime: 런타임 컨텍스트.
|
||||
|
||||
Returns:
|
||||
Updated state with skills_metadata populated.
|
||||
skills_metadata가 채워진 업데이트된 상태.
|
||||
"""
|
||||
# Reload skills on each interaction to catch directory changes
|
||||
# 디렉토리 변경을 캐치하기 위해 각 상호작용마다 스킬 다시 로드
|
||||
skills = list_skills(
|
||||
user_skills_dir=self.skills_dir,
|
||||
project_skills_dir=self.project_skills_dir,
|
||||
@@ -203,25 +203,25 @@ class SkillsMiddleware(AgentMiddleware):
|
||||
request: ModelRequest,
|
||||
handler: Callable[[ModelRequest], ModelResponse],
|
||||
) -> ModelResponse:
|
||||
"""Inject skill documentation into system prompt.
|
||||
"""시스템 프롬프트에 스킬 문서를 주입한다.
|
||||
|
||||
This runs before every model call to ensure skill information is available.
|
||||
스킬 정보가 사용 가능하도록 모든 모델 호출 전에 실행된다.
|
||||
|
||||
Args:
|
||||
request: Model request being processed.
|
||||
handler: Handler function to call with modified request.
|
||||
request: 처리 중인 모델 요청.
|
||||
handler: 수정된 요청으로 호출할 핸들러 함수.
|
||||
|
||||
Returns:
|
||||
Model response from handler.
|
||||
핸들러의 모델 응답.
|
||||
"""
|
||||
# Get skill metadata from state
|
||||
# 상태에서 스킬 메타데이터 가져오기
|
||||
skills_metadata = request.state.get("skills_metadata", [])
|
||||
|
||||
# Format skill locations and list
|
||||
# 스킬 위치와 목록 포맷팅
|
||||
skills_locations = self._format_skills_locations()
|
||||
skills_list = self._format_skills_list(skills_metadata)
|
||||
|
||||
# Format skill documentation
|
||||
# 스킬 문서 포맷팅
|
||||
skills_section = self.system_prompt_template.format(
|
||||
skills_locations=skills_locations,
|
||||
skills_list=skills_list,
|
||||
@@ -239,30 +239,30 @@ class SkillsMiddleware(AgentMiddleware):
|
||||
request: ModelRequest,
|
||||
handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
|
||||
) -> ModelResponse:
|
||||
"""(Async) Inject skill documentation into system prompt.
|
||||
"""(비동기) 시스템 프롬프트에 스킬 문서를 주입한다.
|
||||
|
||||
Args:
|
||||
request: Model request being processed.
|
||||
handler: Handler function to call with modified request.
|
||||
request: 처리 중인 모델 요청.
|
||||
handler: 수정된 요청으로 호출할 핸들러 함수.
|
||||
|
||||
Returns:
|
||||
Model response from handler.
|
||||
핸들러의 모델 응답.
|
||||
"""
|
||||
# State is guaranteed to be SkillsState due to state_schema
|
||||
# state_schema로 인해 상태가 SkillsState임이 보장됨
|
||||
state = cast("SkillsState", request.state)
|
||||
skills_metadata = state.get("skills_metadata", [])
|
||||
|
||||
# Format skill locations and list
|
||||
# 스킬 위치와 목록 포맷팅
|
||||
skills_locations = self._format_skills_locations()
|
||||
skills_list = self._format_skills_list(skills_metadata)
|
||||
|
||||
# Format skill documentation
|
||||
# 스킬 문서 포맷팅
|
||||
skills_section = self.system_prompt_template.format(
|
||||
skills_locations=skills_locations,
|
||||
skills_list=skills_list,
|
||||
)
|
||||
|
||||
# Inject into system prompt
|
||||
# 시스템 프롬프트에 주입
|
||||
if request.system_prompt:
|
||||
system_prompt = request.system_prompt + "\n\n" + skills_section
|
||||
else:
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
"""SubAgents module for research_agent.
|
||||
"""research_agent용 SubAgents 모듈.
|
||||
|
||||
This module implements a Claude Code-inspired SubAgent system with:
|
||||
- Multiple specialized agent types (researcher, explorer, synthesizer)
|
||||
- SubAgent registry for dynamic agent management
|
||||
- Type-based routing via the task tool
|
||||
이 모듈은 Claude Code에서 영감을 받은 SubAgent 시스템을 구현한다:
|
||||
- 다중 전문화 에이전트 타입 (researcher, explorer, synthesizer)
|
||||
- 동적 에이전트 관리를 위한 SubAgent 레지스트리
|
||||
- task 도구를 통한 타입 기반 라우팅
|
||||
|
||||
Architecture:
|
||||
아키텍처:
|
||||
Main Orchestrator Agent
|
||||
├── researcher SubAgent (deep web research)
|
||||
├── explorer SubAgent (fast codebase exploration)
|
||||
└── synthesizer SubAgent (research synthesis)
|
||||
├── researcher SubAgent (심층 웹 연구)
|
||||
├── explorer SubAgent (빠른 코드베이스 탐색)
|
||||
└── synthesizer SubAgent (연구 결과 통합)
|
||||
|
||||
Usage:
|
||||
사용법:
|
||||
from research_agent.subagents import get_all_subagents
|
||||
|
||||
agent = create_deep_agent(
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
"""SubAgent definitions for research_agent.
|
||||
"""research_agent용 SubAgent 정의.
|
||||
|
||||
This module defines specialized SubAgent specifications following the
|
||||
Claude Code subagent_type pattern. Each SubAgent has:
|
||||
- Unique name (used as subagent_type)
|
||||
- Clear description for delegation decisions
|
||||
- Specialized system prompt
|
||||
- Curated tool set
|
||||
- Optional model override
|
||||
이 모듈은 Claude Code subagent_type 패턴을 따르는
|
||||
전문화된 SubAgent 명세를 정의한다. 각 SubAgent는:
|
||||
- 고유 이름 (subagent_type으로 사용)
|
||||
- 위임 결정을 위한 명확한 설명
|
||||
- 전문화된 시스템 프롬프트
|
||||
- 엄선된 도구 세트
|
||||
- 선택적 모델 오버라이드
|
||||
|
||||
SubAgent Types:
|
||||
researcher: Deep web research with reflection
|
||||
explorer: Fast read-only codebase/document exploration
|
||||
synthesizer: Research synthesis and report generation
|
||||
SubAgent 타입:
|
||||
researcher: 반성을 포함한 심층 웹 연구
|
||||
explorer: 빠른 읽기 전용 코드베이스/문서 탐색
|
||||
synthesizer: 연구 통합 및 보고서 생성
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
@@ -22,7 +22,7 @@ from research_agent.prompts import (
|
||||
SYNTHESIZER_INSTRUCTIONS,
|
||||
)
|
||||
|
||||
# Current date for dynamic prompts
|
||||
# 동적 프롬프트용 현재 날짜
|
||||
_current_date = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ EXPLORER_AGENT = {
|
||||
"name": "explorer",
|
||||
"description": "Fast read-only exploration of codebases and documents. Use for finding files, searching patterns, and quick information retrieval. Cannot modify files.",
|
||||
"system_prompt": EXPLORER_INSTRUCTIONS,
|
||||
"tools": [], # Will be populated with read-only tools at runtime
|
||||
"tools": [], # 런타임에 읽기 전용 도구로 채워짐
|
||||
"capabilities": ["explore", "search", "read"],
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ RESEARCHER_AGENT = {
|
||||
"name": "researcher",
|
||||
"description": "Deep web research with reflection. Use for comprehensive topic research, gathering sources, and in-depth analysis. Includes tavily_search and think_tool.",
|
||||
"system_prompt": RESEARCHER_INSTRUCTIONS.format(date=_current_date),
|
||||
"tools": [], # Will be populated with tavily_search, think_tool at runtime
|
||||
"tools": [], # 런타임에 tavily_search, think_tool로 채워짐
|
||||
"capabilities": ["research", "web", "analysis"],
|
||||
}
|
||||
|
||||
@@ -60,25 +60,25 @@ SYNTHESIZER_AGENT = {
|
||||
"name": "synthesizer",
|
||||
"description": "Synthesize multiple research findings into coherent reports. Use for combining sub-agent results, creating summaries, and writing final reports.",
|
||||
"system_prompt": SYNTHESIZER_INSTRUCTIONS,
|
||||
"tools": [], # Will be populated with read_file, write_file, think_tool at runtime
|
||||
"tools": [], # 런타임에 read_file, write_file, think_tool로 채워짐
|
||||
"capabilities": ["synthesize", "write", "analysis"],
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Utility Functions
|
||||
# 유틸리티 함수
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def get_all_subagents() -> list[dict]:
|
||||
"""Get all SubAgent definitions as a list.
|
||||
"""모든 SubAgent 정의를 목록으로 반환한다.
|
||||
|
||||
Returns:
|
||||
List of SubAgent specification dictionaries.
|
||||
SubAgent 명세 딕셔너리 목록.
|
||||
|
||||
Note:
|
||||
Tools are empty and should be populated at agent creation time
|
||||
based on available tools in the runtime.
|
||||
도구는 비어 있으며 런타임에서 사용 가능한 도구를 기반으로
|
||||
에이전트 생성 시 채워져야 한다.
|
||||
"""
|
||||
return [
|
||||
RESEARCHER_AGENT,
|
||||
@@ -88,13 +88,13 @@ def get_all_subagents() -> list[dict]:
|
||||
|
||||
|
||||
def get_subagent_by_name(name: str) -> dict | None:
|
||||
"""Get a specific SubAgent definition by name.
|
||||
"""이름으로 특정 SubAgent 정의를 가져온다.
|
||||
|
||||
Args:
|
||||
name: SubAgent name (e.g., "researcher", "explorer", "synthesizer")
|
||||
name: SubAgent 이름 (예: "researcher", "explorer", "synthesizer")
|
||||
|
||||
Returns:
|
||||
SubAgent specification dict if found, None otherwise.
|
||||
찾으면 SubAgent 명세 딕셔너리, 그렇지 않으면 None.
|
||||
"""
|
||||
agents = {
|
||||
"researcher": RESEARCHER_AGENT,
|
||||
@@ -105,10 +105,10 @@ def get_subagent_by_name(name: str) -> dict | None:
|
||||
|
||||
|
||||
def get_subagent_descriptions() -> str:
|
||||
"""Get formatted descriptions of all SubAgents.
|
||||
"""모든 SubAgent의 포맷된 설명을 가져온다.
|
||||
|
||||
Returns:
|
||||
Formatted string listing all SubAgents and their descriptions.
|
||||
모든 SubAgent와 설명을 나열한 포맷된 문자열.
|
||||
"""
|
||||
descriptions = []
|
||||
for agent in get_all_subagents():
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
"""SubAgent Registry for managing specialized agent types.
|
||||
"""전문화된 에이전트 타입 관리를 위한 SubAgent 레지스트리.
|
||||
|
||||
This module provides a registry pattern inspired by Claude Code's subagent_type system,
|
||||
allowing dynamic registration and lookup of SubAgent specifications.
|
||||
이 모듈은 Claude Code의 subagent_type 시스템에서 영감을 받은 레지스트리 패턴을 제공하며,
|
||||
SubAgent 명세의 동적 등록과 조회를 허용한다.
|
||||
|
||||
The registry supports:
|
||||
- Type-based agent lookup (by name)
|
||||
- Capability-based filtering (by tags)
|
||||
- Runtime agent discovery
|
||||
레지스트리 지원 기능:
|
||||
- 타입 기반 에이전트 조회 (이름으로)
|
||||
- 능력 기반 필터링 (태그로)
|
||||
- 런타임 에이전트 발견
|
||||
|
||||
Example:
|
||||
registry = SubAgentRegistry()
|
||||
registry.register(RESEARCHER_AGENT)
|
||||
registry.register(EXPLORER_AGENT)
|
||||
|
||||
# Get specific agent
|
||||
# 특정 에이전트 가져오기
|
||||
researcher = registry.get("researcher")
|
||||
|
||||
# Get all agents with "research" capability
|
||||
# "research" 능력을 가진 모든 에이전트 가져오기
|
||||
research_agents = registry.get_by_capability("research")
|
||||
"""
|
||||
|
||||
@@ -26,43 +26,43 @@ from typing_extensions import NotRequired
|
||||
|
||||
|
||||
class SubAgentSpec(TypedDict):
|
||||
"""Specification for a SubAgent.
|
||||
"""SubAgent 명세.
|
||||
|
||||
This follows the DeepAgents SubAgent TypedDict pattern with additional
|
||||
fields for capability-based routing.
|
||||
DeepAgents SubAgent TypedDict 패턴을 따르며
|
||||
능력 기반 라우팅을 위한 추가 필드를 포함한다.
|
||||
"""
|
||||
|
||||
name: str
|
||||
"""Unique identifier for the SubAgent (used as subagent_type)."""
|
||||
"""SubAgent의 고유 식별자 (subagent_type으로 사용)."""
|
||||
|
||||
description: str
|
||||
"""Description shown to main agent for delegation decisions."""
|
||||
"""위임 결정을 위해 메인 에이전트에게 표시되는 설명."""
|
||||
|
||||
system_prompt: str
|
||||
"""System prompt defining SubAgent behavior."""
|
||||
"""SubAgent 동작을 정의하는 시스템 프롬프트."""
|
||||
|
||||
tools: list[Any]
|
||||
"""Tools available to this SubAgent."""
|
||||
"""이 SubAgent에서 사용 가능한 도구."""
|
||||
|
||||
model: NotRequired[str]
|
||||
"""Optional model override (defaults to parent's model)."""
|
||||
"""선택적 모델 오버라이드 (기본값은 부모의 모델)."""
|
||||
|
||||
capabilities: NotRequired[list[str]]
|
||||
"""Capability tags for filtering (e.g., ['research', 'web'])."""
|
||||
"""필터링용 능력 태그 (예: ['research', 'web'])."""
|
||||
|
||||
|
||||
class SubAgentRegistry:
|
||||
"""Registry for managing SubAgent specifications.
|
||||
"""SubAgent 명세 관리를 위한 레지스트리.
|
||||
|
||||
This class provides Claude Code-style SubAgent management with:
|
||||
- Registration and deregistration of agents
|
||||
- Name-based lookup (subagent_type matching)
|
||||
- Capability-based filtering
|
||||
이 클래스는 Claude Code 스타일의 SubAgent 관리를 제공한다:
|
||||
- 에이전트 등록 및 등록 해제
|
||||
- 이름 기반 조회 (subagent_type 매칭)
|
||||
- 능력 기반 필터링
|
||||
|
||||
Example:
|
||||
registry = SubAgentRegistry()
|
||||
|
||||
# Register agents
|
||||
# 에이전트 등록
|
||||
registry.register({
|
||||
"name": "researcher",
|
||||
"description": "Deep web research",
|
||||
@@ -71,81 +71,81 @@ class SubAgentRegistry:
|
||||
"capabilities": ["research", "web"],
|
||||
})
|
||||
|
||||
# Lookup by name
|
||||
# 이름으로 조회
|
||||
agent = registry.get("researcher")
|
||||
|
||||
# Filter by capability
|
||||
# 능력으로 필터링
|
||||
web_agents = registry.get_by_capability("web")
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize empty registry."""
|
||||
"""빈 레지스트리를 초기화한다."""
|
||||
self._agents: dict[str, SubAgentSpec] = {}
|
||||
|
||||
def register(self, agent_spec: SubAgentSpec) -> None:
|
||||
"""Register a SubAgent specification.
|
||||
"""SubAgent 명세를 등록한다.
|
||||
|
||||
Args:
|
||||
agent_spec: SubAgent specification dictionary.
|
||||
agent_spec: SubAgent 명세 딕셔너리.
|
||||
|
||||
Raises:
|
||||
ValueError: If agent with same name already registered.
|
||||
ValueError: 같은 이름의 에이전트가 이미 등록된 경우.
|
||||
"""
|
||||
name = agent_spec["name"]
|
||||
if name in self._agents:
|
||||
msg = f"SubAgent '{name}' is already registered"
|
||||
msg = f"SubAgent '{name}'은(는) 이미 등록되어 있습니다"
|
||||
raise ValueError(msg)
|
||||
self._agents[name] = agent_spec
|
||||
|
||||
def unregister(self, name: str) -> None:
|
||||
"""Remove a SubAgent from the registry.
|
||||
"""레지스트리에서 SubAgent를 제거한다.
|
||||
|
||||
Args:
|
||||
name: Name of the SubAgent to remove.
|
||||
name: 제거할 SubAgent 이름.
|
||||
|
||||
Raises:
|
||||
KeyError: If agent not found.
|
||||
KeyError: 에이전트를 찾을 수 없는 경우.
|
||||
"""
|
||||
if name not in self._agents:
|
||||
msg = f"SubAgent '{name}' not found in registry"
|
||||
msg = f"SubAgent '{name}'을(를) 레지스트리에서 찾을 수 없습니다"
|
||||
raise KeyError(msg)
|
||||
del self._agents[name]
|
||||
|
||||
def get(self, name: str) -> SubAgentSpec | None:
|
||||
"""Get a SubAgent specification by name.
|
||||
"""이름으로 SubAgent 명세를 가져온다.
|
||||
|
||||
Args:
|
||||
name: SubAgent name (subagent_type).
|
||||
name: SubAgent 이름 (subagent_type).
|
||||
|
||||
Returns:
|
||||
SubAgent specification if found, None otherwise.
|
||||
찾으면 SubAgent 명세, 그렇지 않으면 None.
|
||||
"""
|
||||
return self._agents.get(name)
|
||||
|
||||
def list_all(self) -> list[SubAgentSpec]:
|
||||
"""List all registered SubAgent specifications.
|
||||
"""등록된 모든 SubAgent 명세를 나열한다.
|
||||
|
||||
Returns:
|
||||
List of all SubAgent specs.
|
||||
모든 SubAgent 명세 목록.
|
||||
"""
|
||||
return list(self._agents.values())
|
||||
|
||||
def list_names(self) -> list[str]:
|
||||
"""List all registered SubAgent names.
|
||||
"""등록된 모든 SubAgent 이름을 나열한다.
|
||||
|
||||
Returns:
|
||||
List of SubAgent names.
|
||||
SubAgent 이름 목록.
|
||||
"""
|
||||
return list(self._agents.keys())
|
||||
|
||||
def get_by_capability(self, capability: str) -> list[SubAgentSpec]:
|
||||
"""Get SubAgents that have a specific capability.
|
||||
"""특정 능력을 가진 SubAgent를 가져온다.
|
||||
|
||||
Args:
|
||||
capability: Capability tag to filter by.
|
||||
capability: 필터링할 능력 태그.
|
||||
|
||||
Returns:
|
||||
List of SubAgents with the specified capability.
|
||||
지정된 능력을 가진 SubAgent 목록.
|
||||
"""
|
||||
return [
|
||||
agent
|
||||
@@ -154,26 +154,26 @@ class SubAgentRegistry:
|
||||
]
|
||||
|
||||
def get_descriptions(self) -> dict[str, str]:
|
||||
"""Get a mapping of agent names to descriptions.
|
||||
"""에이전트 이름과 설명의 매핑을 가져온다.
|
||||
|
||||
Useful for displaying available agents to the main orchestrator.
|
||||
메인 오케스트레이터에 사용 가능한 에이전트를 표시할 때 유용하다.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping agent names to their descriptions.
|
||||
에이전트 이름을 설명에 매핑하는 딕셔너리.
|
||||
"""
|
||||
return {name: agent["description"] for name, agent in self._agents.items()}
|
||||
|
||||
def __contains__(self, name: str) -> bool:
|
||||
"""Check if a SubAgent is registered.
|
||||
"""SubAgent가 등록되어 있는지 확인한다.
|
||||
|
||||
Args:
|
||||
name: SubAgent name to check.
|
||||
name: 확인할 SubAgent 이름.
|
||||
|
||||
Returns:
|
||||
True if agent is registered.
|
||||
에이전트가 등록되어 있으면 True.
|
||||
"""
|
||||
return name in self._agents
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Get number of registered SubAgents."""
|
||||
"""등록된 SubAgent 수를 반환한다."""
|
||||
return len(self._agents)
|
||||
|
||||
@@ -47,17 +47,17 @@ def tavily_search(
|
||||
Literal["general", "news", "finance"], InjectedToolArg
|
||||
] = "general",
|
||||
) -> str:
|
||||
"""Search the web for information on a given query.
|
||||
"""주어진 쿼리로 웹을 검색한다.
|
||||
|
||||
Uses Tavily to discover relevant URLs, then fetches and returns full webpage content as markdown.
|
||||
Tavily를 사용해 관련 URL을 찾고, 전체 웹페이지 콘텐츠를 마크다운으로 가져와 반환한다.
|
||||
|
||||
Args:
|
||||
query: Search query to execute
|
||||
max_results: Maximum number of results to return (default: 1)
|
||||
topic: Topic filter - 'general', 'news', or 'finance' (default: 'general')
|
||||
query: 실행할 검색 쿼리
|
||||
max_results: 반환할 최대 결과 수 (기본값: 1)
|
||||
topic: 주제 필터 - 'general', 'news', 또는 'finance' (기본값: 'general')
|
||||
|
||||
Returns:
|
||||
Formatted search results with full webpage content
|
||||
전체 웹페이지 콘텐츠가 포함된 포맷팅된 검색 결과
|
||||
"""
|
||||
# Tavily 를 사용해 관련 URL 목록을 조회한다
|
||||
search_results = tavily_client.search(
|
||||
@@ -94,27 +94,27 @@ def tavily_search(
|
||||
|
||||
@tool()
|
||||
def think_tool(reflection: str) -> str:
|
||||
"""Tool for strategic reflection on research progress and decision-making.
|
||||
"""연구 진행 상황과 의사결정을 위한 전략적 성찰 도구.
|
||||
|
||||
Use this tool after each search to analyze results and plan next steps systematically.
|
||||
This creates a deliberate pause in the research workflow for quality decision-making.
|
||||
각 검색 후 결과를 분석하고 다음 단계를 체계적으로 계획하기 위해 이 도구를 사용한다.
|
||||
이는 품질 높은 의사결정을 위해 연구 워크플로우에 의도적인 멈춤을 만든다.
|
||||
|
||||
When to use:
|
||||
- After receiving search results: What key information did I find?
|
||||
- Before deciding next steps: Do I have enough to answer comprehensively?
|
||||
- When assessing research gaps: What specific information am I still missing?
|
||||
- Before concluding research: Can I provide a complete answer now?
|
||||
사용 시점:
|
||||
- 검색 결과를 받은 후: 어떤 핵심 정보를 찾았는가?
|
||||
- 다음 단계를 결정하기 전: 포괄적으로 답변할 수 있을 만큼 충분한가?
|
||||
- 연구 공백을 평가할 때: 아직 누락된 구체적인 정보는 무엇인가?
|
||||
- 연구를 마무리하기 전: 지금 완전한 답변을 제공할 수 있는가?
|
||||
|
||||
Reflection should address:
|
||||
1. Analysis of current findings - What concrete information have I gathered?
|
||||
2. Gap assessment - What crucial information is still missing?
|
||||
3. Quality evaluation - Do I have sufficient evidence/examples for a good answer?
|
||||
4. Strategic decision - Should I continue searching or provide my answer?
|
||||
성찰에 포함해야 할 내용:
|
||||
1. 현재 발견의 분석 - 어떤 구체적인 정보를 수집했는가?
|
||||
2. 공백 평가 - 어떤 중요한 정보가 아직 누락되어 있는가?
|
||||
3. 품질 평가 - 좋은 답변을 위한 충분한 증거/예시가 있는가?
|
||||
4. 전략적 결정 - 검색을 계속해야 하는가, 답변을 제공해야 하는가?
|
||||
|
||||
Args:
|
||||
reflection: Your detailed reflection on research progress, findings, gaps, and next steps
|
||||
reflection: 연구 진행 상황, 발견, 공백, 다음 단계에 대한 상세한 성찰
|
||||
|
||||
Returns:
|
||||
Confirmation that reflection was recorded for decision-making
|
||||
의사결정을 위해 성찰이 기록되었다는 확인
|
||||
"""
|
||||
return f"Reflection recorded: {reflection}"
|
||||
return f"성찰 기록됨: {reflection}"
|
||||
|
||||
Reference in New Issue
Block a user