paint-brush
효과적이고 확장 가능한 소프트웨어 개발을 위해 LLM을 활용하는 방법~에 의해@bishalbaral
521 판독값
521 판독값

효과적이고 확장 가능한 소프트웨어 개발을 위해 LLM을 활용하는 방법

~에 의해 Bishal11m2024/08/04
Read on Terminal Reader

너무 오래; 읽다

YCombinator CEO Garry Tan은 Claude 3.5 Sonnet을 사용한 코드 작성에 대한 YCombinator Reddit 커뮤니티 회원의 게시물을 트윗했습니다. 이 게시물에서는 일상적인 LLM 사용의 중요한 부분으로서 건전한 아키텍처 및 인프라 결정을 내리는 것이 중요하다는 점을 강조했습니다.
featured image - 효과적이고 확장 가능한 소프트웨어 개발을 위해 LLM을 활용하는 방법
Bishal HackerNoon profile picture
0-item
1-item

최근 X와 Reddit 사용자들은 Claude 3.5 Sonnet으로 코드를 작성하면서 "매우 강력하다"는 느낌을 받았다고 보고했습니다. 특히 주목을 받은 게시물 중 하나는 YCombinator CEO인 Garry Tan의 트윗이었습니다.


Garry는 Claude 3.5 Sonnet을 사용하여 인기 있는 기능을 구현하면서 생산성을 10배로 높인 방법에 대해 YCombinator Reddit 커뮤니티 회원 중 한 명이 다음 게시물을 공유했습니다.


이 게시물은 또한 일상적인 LLM 사용의 중요한 부분으로서 건전한 아키텍처 및 인프라 결정을 내리는 것이 중요하다는 점을 강조했습니다.


Claude 3.5와 같은 LLM은 뛰어난 이점을 제공하지만 메모리 및 컨텍스트 유지와 관련하여 여전히 한계가 있습니다. 이러한 제한 사항을 해결하기 위해 여러 가지 개발 전략을 사용할 수 있습니다. 이러한 전략은 모든 개발자가 경험을 통해 연마하지만 일반 영어로 LLM을 요청할 때 종종 놓치기 쉬운 소프트웨어 개발 기초를 중심으로 진행됩니다.


Reddit 작성자가 아키텍처 결정이라고 부르는 개발자가 매일 사용하는 것과 동일한 기본 원칙을 LLM 상호 작용에 적용하면 고도로 모듈화되고 확장 가능하며 잘 문서화된 코드가 생성될 수 있습니다.


다음은 Python의 실제 예제와 함께 LLM 지원 소프트웨어 개발에 적용할 수 있는 몇 가지 주요 코딩 원칙 및 개발 사례입니다.

예제의 모든 프롬프트는 Claude Sonnet 3.5에서 사용되었습니다.

구성 요소 기반 아키텍처

각 논리 구성 요소를 LLM에 더 쉽게 설명하고 정확한 구성 요소를 얻으려면 코드베이스를 작고 잘 정의된 구성 요소로 나누십시오.

 # database.py class Database: def __init__(self, sql_connection_string): .... def query(self, sql): .... # user_service.py class UserService: def __init__(self, database): self.db = database def get_user(self, user_id): return self.db.query(f"SELECT * FROM users WHERE id = {user_id}") # main.py db = Database("sql_connection_string") user_service = UserService(db) user = user_service.get_user(123)

추상화 레이어

복잡한 구현을 숨기려면 추상화 레이어를 사용하고 그 과정에서 기록된 각 구성 요소에 대한 세부 정보를 사용하여 더 높은 수준의 추상화에 집중하세요.

 # top-level abstraction class BankingSystem: def __init__(self): self._account_manager = AccountManager() self._transaction_processor = TransactionProcessor() def create_account(self, acct_number: str, owner: str) -> None: self._account_manager.create_account(acct_number, owner) def process_transaction(self, acct_number: str, transaction_type: str, amount: float) -> None: account = self._account_manager.get_account(acct_number) self._transaction_processor.process(account, transaction_type, amount) # mid-level abstractions class AccountManager: def __init__(self): def create_account(self, acct_number: str, owner: str) -> None: def get_account(self, acct_number: str) -> 'Account': class TransactionProcessor: def process(self, account: 'Account', transaction_type: str, amount: float) -> None: # lower-level abstractions class Account(ABC): .... class Transaction(ABC): .... # concrete implementations class SavingsAccount(Account): .... class CheckingAccount(Account): .... class DepositTransaction(Transaction): .... class WithdrawalTransaction(Transaction): .... # lowest-level abstraction class TransactionLog: .... # usage focuses on the high-level abstraction cart = ShoppingCart() cart.add_item(Item("Book", 15)) cart.add_item(Item("Pen", 2)) total = cart.get_total()

깔끔한 인터페이스 정의

LLM에 도움을 요청할 때 작업을 보다 쉽게 관리하려면 각 구성 요소에 대해 명확한 인터페이스를 정의하고 모두 별도로 구현하는 데 집중하세요.

 class PaymentProcessor(ABC): @abstractmethod def process_payment(self, amount: float, card_no: str) -> bool: .... class StripeProcessor(PaymentProcessor): # stripe specific implementation def process_payment(self, amount: float, card_no: str) -> bool: .... class PayPalProcessor(PaymentProcessor): # paypal specific implementation def process_payment(self, amount: float, card_no: str) -> bool: ....

단일 책임 원칙

환각을 방지하고 범위를 제한하려면 한 번에 하나의 작은 부분에 집중하고 각 클래스/기능에 잘 정의된 단일 책임이 있는지 확인하십시오. 생성되는 코드를 더 효과적으로 제어하려면 점진적으로 개발하세요.

 class UserManager: # user creation logic def create_user(self, username, email): ... class EmailService: # send welcome email logic def send_welcome_email(self, email): .... class NotificationService: # send sms notification def send_sms(self, username, email): ... # Usage user_manager = UserManager() email_svc = EmailService() user = user_manager.create_user("hacker", "[email protected]") email_svc.send_welcome_email("[email protected]")

일관된 명명 규칙

LLM에 대한 코드 구조 설명을 단순화하고 제안 사항을 이해하려면 명확하고 일관된 명명 규칙을 사용하십시오.

 # classes: PascalCase class UserAccount: pass # functions and variables: snake_case def calculate_total_price(item_price, quantity): total_cost = item_price * quantity return total_cost # constants: UPPERCASE_WITH_UNDERSCORES MAX_LOGIN_ATTEMPTS = 3 # private methods/variables: prefix with underscore class DatabaseConnection: def __init__(self): self._connection = None def _connect_to_database(self): pass

코드 템플릿

요구 사항을 기반으로 특정 구현을 생성하려면 공통 코드 구조에 대한 뼈대 코드를 생성하고 이를 시작 코드로 사용합니다.

 # Todo item - pydantic Model class TodoItem(BaseModel): id: Optional[int] = None title: str description: Optional[str] = None completed: bool = False todo_db = [<insert mock data>] @app.get("/todos", response_model=List[TodoItem]) async def get_todos(): # TODO: retrieve all todos @app.get("/todos/{todo_id}", response_model=TodoItem) async def get_todo(todo_id: int): # TODO: retrieve specific todo @app.post("/todos", response_model=TodoItem) async def create_todo(todo: TodoItem): # TODO: create todo @app.put("/todos/{todo_id}", response_model=TodoItem) async def update_todo(todo_id: int, todo: TodoItem): # TODO: update todo @app.delete("/todos/{todo_id}") async def delete_todo(todo_id: int): # TODO: delete todo

문서 중심 개발

LLM을 안내하는 데 도움이 되도록 구현하기 전에 각 구성 요소에 대한 간결한 문서를 작성하세요. 또한 LLM 자체를 사용하여 향후 프롬프트를 안내할 수 있는 독스트링을 생성하세요.


즉각적인:

 Use a Documentation Driven Development approach to write clear, concise docstrings for the following python function calculate_discount(price, discount_percentage) takes in - price (float), original price of the item. - discount_percentage (float), discount percentage range(0-100). returns - price after discount (float) raises - ValueError, when discount_percentage is out of range example - calculate_discount(100, 10) -> 10.0


응답:

 def calculate_discount(price: float, discount_percent: float) -> float: """ Calculate the discounted price of an item. Args: price (float): original price for the item. discount_percentage (float): discount percentage range(0-100). Returns: float: The price after applying the discount. Raises: ValueError: If discount_percentage is not between 0 and 100. Example: >>> calculate_discount(100, 20) 80.0 """ if not 0 <= discount_percent <= 100: raise ValueError("Discount percent must be between 0 and 100") discount_amount = price * (discount_percent / 100) return price - discount_amount

테스트 주도 개발

LLM의 구현을 돕고 특히 극단적인 경우를 처리하려면 실제 기능을 구현하기 전에 테스트를 직접 작성하거나 LLM을 사용하여 수행하세요. LLM은 내부적으로 코드를 실행하지 않지만 생성된 코드가 기능 요구 사항을 충족하는지 확인하기 위해 사전에 테스트를 수행하는 것은 LLM 생성 코드에서 버그를 잡는 데 유용할 수 있습니다.


테스트 케이스:

 def is_palindrome(s: str) -> bool: """check if string is palindrome.""" return s == s[::-1] class TestPalindrome(unittest.TestCase): def test_palindrome(self): self.assertTrue(is_palindrome("racecar")) self.assertTrue(is_palindrome("A man a plan a canal Panama")) self.assertFalse(is_palindrome("hello")) self.assertTrue(is_palindrome("")) # Edge case: empty string


즉각적인:

 given the test case <insert test>, use Document Driven Development to write a function for TestPalindrome


응답:

 def is_palindrome(s: str) -> bool: """ Check if a string is a palindrome. A palindrome is a word, phrase, number, or other sequence of characters that reads the same forward and backward, ignoring spaces, punctuation, and capitalization. Args: s (str): The input string to check. Returns: bool: True if the input is a palindrome, False otherwise. Examples: >>> is_palindrome("racecar") True >>> is_palindrome("A man a plan a canal Panama") True >>> is_palindrome("hello") False >>> is_palindrome("") True """ # Remove non-alphanumeric characters and convert to lowercase cleaned = ''.join(char.lower() for char in s if char.isalnum()) # Compare the cleaned string with its reverse return cleaned == cleaned[::-1]

디자인 패턴 라이브러리

LLM과 함께 여러 구현 세부 사항을 논의하려면 참조용으로 일반적으로 사용되는 디자인 패턴 라이브러리를 유지하십시오.

 # Singleton pattern class DatabaseConnection: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) # initialize database connection return cls._instance # Factory pattern class AnimalFactory: @staticmethod def create_animal(animal_type): if animal_type == "dog": return Dog() elif animal_type == "cat": return Cat() else: raise ValueError("Unknown animal type") # Observer pattern class Subject: def __init__(self): self._observers = [] def attach(self, observer): self._observers.append(observer) def detach(self, observer): self._observers.remove(observer) def notify(self): for observer in self._observers: observer.update() # Adapter pattern class LLMAdapter: def __init__(self, llm_service): self.llm_service = llm_service def generate_code(self, prompt): llm_response = self.llm_service.complete(prompt) return self.extract_code(llm_response) def extract_code(self, response): pass

코드 검토 체크리스트

품질과 일관성을 보장하려면 LLM 생성 코드 검토를 위한 체크리스트를 만드세요.

 # Code Review Checklist ## Functionality - [ ] Code performs the intended task correctly - [ ] Edge cases are handled appropriately ## Code Quality - [ ] Code follows project's style guide - [ ] Variable and function names are descriptive and consistent - [ ] No unnecessary comments or dead code ## Performance - [ ] Code is optimized for efficiency - [ ] No potential performance bottlenecks ## Security - [ ] Input validation is implemented - [ ] Sensitive data is handled securely ## Testing - [ ] Unit tests are included and pass - [ ] Edge cases are covered in tests ## Documentation - [ ] Functions and classes are properly documented - [ ] Complex logic is explained in comments

모듈식 프롬프트

LLM은 정의된 구조에서 가장 잘 작동하므로 코딩 작업을 더 작은 프롬프트로 나누기 위한 전략을 개발하십시오. 체계적인 접근 방식을 따르면 LLM이 생성된 코드를 여러 번 다시 수정하라는 메시지를 표시하지 않고 작업 코드를 생성하는 데 도움이 됩니다.


즉각적인:

 I need to implement a function to calculate the Fibonacci number sequence using a Document Driven Development approach. 1. Purpose: function that generates the Fibonacci sequence up to a given number of terms. 2. Interface: def fibonacci_seq(n: int) -> List[int]: """ generate Fibonacci sequence up to n terms. Args: n (int): number of terms in the sequence Returns: List[int]: fibonacci sequence """ 3. Key Functionalities: - handle input validation (n should always be a positive integer) - generate the sequence starting with 0 and 1 - each subsequent number is the sum of two preceding ones - return the sequence as a list 4. Implementation Details: - use a loop to generate the sequence - store the sequence in a list - optimize for memory by only keeping the last two numbers in memory if needed 5. Test Cases: - fibonacci_seq(0) should return [] - fibonacci_seq(1) should return [0] - fibonacci_seq(5) should return [0, 1, 1, 2, 3]

위의 모든 예는 간단해 보일 수 있지만 모듈식 아키텍처 및 효과적인 프롬프트 엔지니어링과 같은 기본 관행을 따르고 LLM 지원 개발에서 견고하고 구조화된 접근 방식을 취하면 규모 면에서 큰 차이를 만듭니다. 이러한 관행을 구현함으로써 더 많은 개발자가 LLM 사용의 이점을 극대화하여 생산성과 코드 품질을 향상시킬 수 있습니다.


LLM은 간과하기 쉬운 소프트웨어 엔지니어링 원칙에 따라 가장 잘 작동하는 강력한 도구입니다. 그것들을 내부화하는 것은 우아하게 만들어진 코드와 무작위로 생성된 커다란 진흙 공 사이의 차이일 수 있습니다.


이 문서의 목표는 개발자가 LLM을 사용하여 고품질 코드를 생성하고 향후 시간을 절약하는 동안 항상 이러한 관행을 염두에 두도록 권장하는 것입니다. LLM이 지속적으로 개선됨에 따라 LLM을 최대한 활용하는 데 있어 기초가 더욱 중요해질 것입니다.


소프트웨어 개발 원칙에 대해 더 자세히 알아보려면 Robert C. Martin이 쓴 고전 교과서인 Clean Architecture: A Craftsman's Guide to Software Structure and Design을 확인하세요.


이 기사가 마음에 드셨다면 LLM 지원 개발을 위한 자세한 워크플로를 살펴보는 다음 기사를 계속 지켜봐 주시기 바랍니다. 그 밖에 중요하다고 생각하는 컨셉이 있다면 댓글로 공유해주세요! 감사합니다.