Недавно пользователи X и Reddit сообщили, что написание кода с помощью Claude 3.5 Sonnet позволило им почувствовать себя «очень сильными». Одним из таких постов, который особенно привлек внимание, стал твит генерального директора YCombinator Гарри Тана.
Гарри поделился следующей публикацией одного из участников сообщества YCombinator Reddit о том, как использование Claude 3.5 Sonnet увеличило их производительность в 10 раз при реализации популярных функций.
В сообщении также подчеркивается важность принятия обоснованных архитектурных и инфраструктурных решений как важнейшей части повседневного использования LLM.
Хотя LLM, такие как Claude 3.5, предлагают выдающиеся преимущества, у них все еще есть ограничения в отношении сохранения памяти и контекста. Для устранения этих ограничений можно использовать несколько стратегий развития. Эти стратегии основаны на основах разработки программного обеспечения, которые все разработчики оттачивают с опытом, но их часто легко упустить, предлагая LLM на простом английском языке.
Применение тех же основных принципов, которые разработчики Reddit используют ежедневно (которые автор Reddit называет архитектурными решениями), к взаимодействиям LLM может привести к созданию высокомодульного, масштабируемого и хорошо документированного кода.
Ниже приведены некоторые ключевые принципы кодирования и методы разработки, которые можно применять при разработке программного обеспечения с помощью LLM, а также практические примеры на Python:
Все подсказки в примерах использовались в 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. Пожалуйста, поделитесь любыми другими концепциями, которые вы считаете важными, в комментариях! Спасибо.