diff --git a/.gitignore b/.gitignore index 335ebe0..192e22f 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ CLAUDE.md GEMINI.md QWEN.md .serena/ + +# Others +.DS_Store +.vscode/ +*.pdf \ No newline at end of file diff --git a/DeepAgents_Technical_Guide.md b/DeepAgents_Technical_Guide.md index f8811d2..a4a2885 100644 --- a/DeepAgents_Technical_Guide.md +++ b/DeepAgents_Technical_Guide.md @@ -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 상세 diff --git a/README.md b/README.md index faaa221..2c04cb7 100644 --- a/README.md +++ b/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_20_paradigm](./agent_20_paradigm.png) + +## Agent 1.0 vs Agent 2.0 + +![agent_versus_10_20](./agent_versus_10_20.jpeg) ## 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) diff --git a/agent_20_paradigm.png b/agent_20_paradigm.png new file mode 100644 index 0000000..584258d Binary files /dev/null and b/agent_20_paradigm.png differ diff --git a/agent_versus_10_20.jpeg b/agent_versus_10_20.jpeg new file mode 100644 index 0000000..60c48c3 Binary files /dev/null and b/agent_versus_10_20.jpeg differ diff --git a/research_agent/agent.py b/research_agent/agent.py index 163a39e..e9ffca2 100644 --- a/research_agent/agent.py +++ b/research_agent/agent.py @@ -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 생성 # ============================================================================= # # 통합 구성: diff --git a/research_agent/prompts.py b/research_agent/prompts.py index 72ebec8..1b5f543 100644 --- a/research_agent/prompts.py +++ b/research_agent/prompts.py @@ -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. diff --git a/research_agent/researcher/__init__.py b/research_agent/researcher/__init__.py index 0479c40..b8a75f6 100644 --- a/research_agent/researcher/__init__.py +++ b/research_agent/researcher/__init__.py @@ -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 ( diff --git a/research_agent/researcher/agent.py b/research_agent/researcher/agent.py index a6abcf7..f51be58 100644 --- a/research_agent/researcher/agent.py +++ b/research_agent/researcher/agent.py @@ -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 diff --git a/research_agent/skills/__init__.py b/research_agent/skills/__init__.py index 1496d3c..dd0e3ba 100644 --- a/research_agent/skills/__init__.py +++ b/research_agent/skills/__init__.py @@ -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 diff --git a/research_agent/skills/load.py b/research_agent/skills/load.py index 409c363..0e077b9 100644 --- a/research_agent/skills/load.py +++ b/research_agent/skills/load.py @@ -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()) diff --git a/research_agent/skills/middleware.py b/research_agent/skills/middleware.py index a569e51..1b1adff 100644 --- a/research_agent/skills/middleware.py +++ b/research_agent/skills/middleware.py @@ -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: diff --git a/research_agent/subagents/__init__.py b/research_agent/subagents/__init__.py index 58bb75e..d3c2a88 100644 --- a/research_agent/subagents/__init__.py +++ b/research_agent/subagents/__init__.py @@ -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( diff --git a/research_agent/subagents/definitions.py b/research_agent/subagents/definitions.py index 8dfc738..798044a 100644 --- a/research_agent/subagents/definitions.py +++ b/research_agent/subagents/definitions.py @@ -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(): diff --git a/research_agent/subagents/registry.py b/research_agent/subagents/registry.py index 4350a5c..b59cfcc 100644 --- a/research_agent/subagents/registry.py +++ b/research_agent/subagents/registry.py @@ -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) diff --git a/research_agent/tools.py b/research_agent/tools.py index 2f799ca..f29bacd 100644 --- a/research_agent/tools.py +++ b/research_agent/tools.py @@ -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}"