From d21c0c7549a1a0e9ea30b89cf6637327afb99e71 Mon Sep 17 00:00:00 2001 From: Cosmin Paunel Date: Thu, 26 Feb 2026 14:52:53 +0000 Subject: [PATCH] refactor: simplify init middleware to generate AGENTS.md and CLAUDE.md only - Restore AGENTS.md generation from uipath_langchain._resources - Remove generation of .agent/ reference docs (these are now handled by the uipath-claude-plugins repo) - Update AGENTS.md content to remove references to removed sub-docs - CLAUDE.md continues to be generated from uipath._resources (@AGENTS.md) - Bump version to 0.7.7 Co-Authored-By: Claude Opus 4.6 --- pyproject.toml | 2 +- src/uipath_langchain/_cli/cli_init.py | 10 +- src/uipath_langchain/_resources/AGENTS.md | 18 +--- .../_resources/REQUIRED_STRUCTURE.md | 92 ------------------ tests/cli/test_init.py | 94 ++++--------------- uv.lock | 2 +- 6 files changed, 24 insertions(+), 194 deletions(-) delete mode 100644 src/uipath_langchain/_resources/REQUIRED_STRUCTURE.md diff --git a/pyproject.toml b/pyproject.toml index f75c572f6..4ed3b3fc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-langchain" -version = "0.8.4" +version = "0.8.5" description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/src/uipath_langchain/_cli/cli_init.py b/src/uipath_langchain/_cli/cli_init.py index a797dcc7e..c3bce0ddc 100644 --- a/src/uipath_langchain/_cli/cli_init.py +++ b/src/uipath_langchain/_cli/cli_init.py @@ -70,10 +70,10 @@ def generate_agent_md_file( def generate_specific_agents_md_files( target_directory: str, no_agents_md_override: bool ) -> Generator[tuple[str, FileOperationStatus], None, None]: - """Generate agent-specific files from the packaged resource. + """Generate CLAUDE.md file from the packaged resource. Args: - target_directory: The directory where the files should be created. + target_directory: The directory where the file should be created. no_agents_md_override: Whether to override existing files. Yields: @@ -82,15 +82,9 @@ def generate_specific_agents_md_files( - UPDATED: File was overwritten - SKIPPED: File exists and was not overwritten """ - agent_dir = os.path.join(target_directory, ".agent") - os.makedirs(agent_dir, exist_ok=True) - file_configs = [ (target_directory, "CLAUDE.md", "uipath._resources"), - (agent_dir, "CLI_REFERENCE.md", "uipath._resources"), - (agent_dir, "SDK_REFERENCE.md", "uipath._resources"), (target_directory, "AGENTS.md", "uipath_langchain._resources"), - (agent_dir, "REQUIRED_STRUCTURE.md", "uipath_langchain._resources"), ] for directory, file_name, resource_name in file_configs: diff --git a/src/uipath_langchain/_resources/AGENTS.md b/src/uipath_langchain/_resources/AGENTS.md index c1f02680a..4626d7175 100644 --- a/src/uipath_langchain/_resources/AGENTS.md +++ b/src/uipath_langchain/_resources/AGENTS.md @@ -2,20 +2,4 @@ This document provides practical code patterns for building UiPath coded agents using LangGraph and the UiPath Python SDK. ---- - -## Documentation Structure - -This documentation is split into multiple files for efficient context loading. Load only the files you need: - -1. **@.agent/REQUIRED_STRUCTURE.md** - Agent structure patterns and templates - - **When to load:** Creating a new agent or understanding required patterns - - **Contains:** Required Pydantic models (Input, State, Output), LLM initialization patterns, standard agent template - -2. **@.agent/SDK_REFERENCE.md** - Complete SDK API reference - - **When to load:** Calling UiPath SDK methods, working with services (actions, assets, jobs, etc.) - - **Contains:** All SDK services and methods with full signatures and type annotations - -3. **@.agent/CLI_REFERENCE.md** - CLI commands documentation - - **When to load:** Working with `uipath init`, `uipath run`, or `uipath eval` commands - - **Contains:** Command syntax, options, usage examples, and workflows +Run with: `uipath run main '{...}'` diff --git a/src/uipath_langchain/_resources/REQUIRED_STRUCTURE.md b/src/uipath_langchain/_resources/REQUIRED_STRUCTURE.md deleted file mode 100644 index a8d245123..000000000 --- a/src/uipath_langchain/_resources/REQUIRED_STRUCTURE.md +++ /dev/null @@ -1,92 +0,0 @@ -## Required Agent Structure - -**IMPORTANT**: All UiPath coded agents MUST follow this standard structure unless explicitly specified otherwise by the user. - -### Required Components - -Every agent implementation MUST include these three Pydantic models: - -```python -from pydantic import BaseModel - -class Input(BaseModel): - """Define input fields that the agent accepts""" - # Add your input fields here - pass - -class State(BaseModel): - """Define the agent's internal state that flows between nodes""" - # Add your state fields here - pass - -class Output(BaseModel): - """Define output fields that the agent returns""" - # Add your output fields here - pass -``` - -### Required LLM Initialization - -Unless the user explicitly requests a different LLM provider, always use `UiPathChat`: - -```python -from uipath_langchain.chat import UiPathChat - -llm = UiPathChat(model="gpt-4.1-mini-2025-04-14", temperature=0.7) -``` - -**Alternative LLMs** (only use if explicitly requested): -- `ChatOpenAI` from `langchain_openai` -- `ChatAnthropic` from `langchain_anthropic` -- Other LangChain-compatible LLMs - -### Standard Agent Template - -Every agent should follow this basic structure: - -```python -from langchain_core.messages import SystemMessage, HumanMessage -from langgraph.graph import START, StateGraph, END -from uipath_langchain.chat import UiPathChat -from pydantic import BaseModel - -# 1. Define Input, State, and Output models -class Input(BaseModel): - field: str - -class State(BaseModel): - field: str - result: str = "" - -class Output(BaseModel): - result: str - -# 2. Initialize UiPathChat LLM -llm = UiPathChat(model="gpt-4.1-mini-2025-04-14", temperature=0.7) - -# 3. Define agent nodes (async functions) -async def process_node(state: State) -> State: - response = await llm.ainvoke([HumanMessage(state.field)]) - return State(field=state.field, result=response.content) - -async def output_node(state: State) -> Output: - return Output(result=state.result) - -# 4. Build the graph -builder = StateGraph(State, input=Input, output=Output) -builder.add_node("process", process_node) -builder.add_node("output", output_node) -builder.add_edge(START, "process") -builder.add_edge("process", "output") -builder.add_edge("output", END) - -# 5. Compile the graph -graph = builder.compile() -``` - -**Key Rules**: -1. Always use async/await for all node functions -2. All nodes (except output) must accept and return `State` -3. The final output node must return `Output` -4. Use `StateGraph(State, input=Input, output=Output)` for initialization -5. Always compile with `graph = builder.compile()` diff --git a/tests/cli/test_init.py b/tests/cli/test_init.py index f7a2f0a8a..1de1cf1c5 100644 --- a/tests/cli/test_init.py +++ b/tests/cli/test_init.py @@ -12,7 +12,7 @@ class TestGenerateAgentMdFile: """Tests for the generate_agent_md_file function.""" def test_generate_file_success(self): - """Test successfully generating an agent MD file.""" + """Test successfully generating AGENTS.md file.""" with tempfile.TemporaryDirectory() as temp_dir: result = generate_agent_md_file( temp_dir, "AGENTS.md", "uipath_langchain._resources", False @@ -27,7 +27,7 @@ def test_generate_file_success(self): with open(target_path, "r") as f: content = f.read() assert len(content) > 0 - assert "Agent Code Patterns Reference" in content + assert "LangGraph" in content def test_file_already_exists(self): """Test that an existing file is overwritten.""" @@ -49,26 +49,7 @@ def test_file_already_exists(self): content = f.read() assert content != original_content - assert "Agent Code Patterns Reference" in content - - def test_generate_required_structure_file(self): - """Test generating REQUIRED_STRUCTURE.md file.""" - with tempfile.TemporaryDirectory() as temp_dir: - agent_dir = os.path.join(temp_dir, ".agent") - os.makedirs(agent_dir, exist_ok=True) - result = generate_agent_md_file( - agent_dir, "REQUIRED_STRUCTURE.md", "uipath_langchain._resources", False - ) - assert result is not None - file_name, status = result - assert file_name == "REQUIRED_STRUCTURE.md" - assert status == FileOperationStatus.CREATED - - target_path = os.path.join(agent_dir, "REQUIRED_STRUCTURE.md") - assert os.path.exists(target_path) - with open(target_path, "r") as f: - content = f.read() - assert "Required Agent Structure" in content + assert "LangGraph" in content def test_file_skipped_when_no_override(self): """Test that an existing file is skipped when no_agents_md_override is True.""" @@ -95,54 +76,33 @@ def test_file_skipped_when_no_override(self): class TestGenerateSpecificAgentsMdFiles: """Tests for the generate_specific_agents_md_files function.""" - def test_generate_all_files(self): - """Test that all agent documentation files are generated.""" + def test_generate_agents_and_claude_md(self): + """Test that AGENTS.md and CLAUDE.md are generated (without .agent/ sub-docs).""" with tempfile.TemporaryDirectory() as temp_dir: results = list(generate_specific_agents_md_files(temp_dir, False)) - # Check that we got results for all files - assert len(results) == 5 + # Check that we got results for AGENTS.md and CLAUDE.md + assert len(results) == 2 file_names = [name for name, _ in results] assert "AGENTS.md" in file_names - assert "REQUIRED_STRUCTURE.md" in file_names assert "CLAUDE.md" in file_names - assert "CLI_REFERENCE.md" in file_names - assert "SDK_REFERENCE.md" in file_names - # Check all were created (not updated or skipped) + # Should NOT create .agent directory + assert not os.path.exists(os.path.join(temp_dir, ".agent")) + + # Check files were created (not updated or skipped) for _, status in results: assert status == FileOperationStatus.CREATED - agent_dir = os.path.join(temp_dir, ".agent") - assert os.path.exists(agent_dir) - assert os.path.isdir(agent_dir) - agents_md_path = os.path.join(temp_dir, "AGENTS.md") assert os.path.exists(agents_md_path) - required_structure_path = os.path.join(agent_dir, "REQUIRED_STRUCTURE.md") - assert os.path.exists(required_structure_path) - with open(agents_md_path, "r") as f: agents_content = f.read() - assert "Agent Code Patterns Reference" in agents_content - - with open(required_structure_path, "r") as f: - required_content = f.read() - assert "Required Agent Structure" in required_content - - def test_agent_dir_already_exists(self): - """Test that the existing .agent directory doesn't cause errors.""" - with tempfile.TemporaryDirectory() as temp_dir: - agent_dir = os.path.join(temp_dir, ".agent") - os.makedirs(agent_dir, exist_ok=True) - - results = list(generate_specific_agents_md_files(temp_dir, False)) - assert len(results) == 5 - assert os.path.exists(agent_dir) + assert "LangGraph" in agents_content def test_files_overwritten(self): - """Test that existing files are overwritten.""" + """Test that existing AGENTS.md is overwritten.""" with tempfile.TemporaryDirectory() as temp_dir: agents_md_path = os.path.join(temp_dir, "AGENTS.md") original_content = "Custom documentation" @@ -151,7 +111,7 @@ def test_files_overwritten(self): results = list(generate_specific_agents_md_files(temp_dir, False)) - # Check that AGENTS.md was updated, others were created + # Check that AGENTS.md was updated agents_result = [r for r in results if r[0] == "AGENTS.md"] assert len(agents_result) == 1 _, status = agents_result[0] @@ -161,42 +121,26 @@ def test_files_overwritten(self): content = f.read() assert content != original_content - assert "Agent Code Patterns Reference" in content + assert "LangGraph" in content def test_files_skipped_when_no_override(self): - """Test that existing files are skipped when no_agents_md_override is True.""" + """Test that existing AGENTS.md is skipped when no_agents_md_override is True.""" with tempfile.TemporaryDirectory() as temp_dir: - # Create some existing files + # Create existing AGENTS.md agents_md_path = os.path.join(temp_dir, "AGENTS.md") - claude_md_path = os.path.join(temp_dir, "CLAUDE.md") with open(agents_md_path, "w") as f: f.write("Existing AGENTS.md") - with open(claude_md_path, "w") as f: - f.write("Existing CLAUDE.md") results = list(generate_specific_agents_md_files(temp_dir, True)) - # Check that existing files were skipped + # Check that existing file was skipped skipped_files = [ name for name, status in results if status == FileOperationStatus.SKIPPED ] assert "AGENTS.md" in skipped_files - assert "CLAUDE.md" in skipped_files - - # Check that non-existing files were created - created_files = [ - name - for name, status in results - if status == FileOperationStatus.CREATED - ] - assert "CLI_REFERENCE.md" in created_files - assert "SDK_REFERENCE.md" in created_files - assert "REQUIRED_STRUCTURE.md" in created_files - # Verify the existing files were not modified + # Verify the existing file was not modified with open(agents_md_path, "r") as f: assert f.read() == "Existing AGENTS.md" - with open(claude_md_path, "r") as f: - assert f.read() == "Existing CLAUDE.md" diff --git a/uv.lock b/uv.lock index 2cca4149e..bee6554d3 100644 --- a/uv.lock +++ b/uv.lock @@ -3324,7 +3324,7 @@ wheels = [ [[package]] name = "uipath-langchain" -version = "0.8.4" +version = "0.8.5" source = { editable = "." } dependencies = [ { name = "httpx" },