مستندسازی کد و API با هوش مصنوعی؛ ساخت AI Documentation Generator

در این آموزش یک AI Documentation Generator واقعی می‌سازید که OpenAPI و کد پروژه را تحلیل می‌کند، مستندات فارسی API، مثال‌های curl و Python می‌سازد و مغایرت مستندات با قرارداد را تشخیص می‌دهد.

Share
مستندسازی کد و API با هوش مصنوعی؛ ساخت AI Documentation Generator

مستندسازی کد و API با هوش مصنوعی؛ ساخت مولد مستندات خودکار

مستندات نرم‌افزار معمولاً در روز اول پروژه دقیق‌اند، اما با تغییر کد به‌تدریج قدیمی می‌شوند. توسعه‌دهنده Endpoint جدیدی اضافه می‌کند، نام یک فیلد تغییر می‌کند یا پاسخ خطای جدیدی ساخته می‌شود، اما مستندات به‌روزرسانی نمی‌شوند.

نتیجه این وضعیت:

  • کاربران API درخواست اشتباه ارسال می‌کنند.
  • اعضای جدید تیم برای شناخت پروژه زمان زیادی صرف می‌کنند.
  • مثال‌های قدیمی دیگر اجرا نمی‌شوند.
  • رفتار واقعی کد با README متفاوت می‌شود.
  • تیم پشتیبانی بارها به سؤال‌های تکراری پاسخ می‌دهد.
  • توسعه‌دهندگان برای فهم یک تابع مجبور به خواندن تمام پیاده‌سازی می‌شوند.
  • تغییرات مهم Release بدون توضیح باقی می‌مانند.

هوش مصنوعی می‌تواند از روی Source Code، OpenAPI Schema، تست‌ها و توضیحات تغییر، پیش‌نویس مستندات تولید کند. اما مستندسازی خودکار زمانی قابل اعتماد است که:

  1. منبع حقیقت مشخص باشد.
  2. مدل اجازه نداشته باشد رفتار جدید اختراع کند.
  3. مثال‌های کد اجرا یا اعتبارسنجی شوند.
  4. مستندات تولیدشده با Schema تطبیق داده شوند.
  5. تغییرات مستندات در Code Review بررسی شوند.
  6. نسخه‌بندی و تشخیص Drift وجود داشته باشد.

در این آموزش یک ابزار عملی با Python و API درواره می‌سازیم که OpenAPI یک برنامه FastAPI را می‌خواند و برای هر Endpoint موارد زیر را تولید می‌کند:

  • توضیح فارسی
  • پارامترها
  • بدنه درخواست
  • پاسخ موفق
  • خطاهای مستندشده
  • مثال curl
  • مثال Python
  • نکات استفاده
  • پرسش‌ها و ابهام‌های موجود در Schema

مستندسازی با هوش مصنوعی چیست؟

AI Documentation یعنی استفاده از مدل‌های زبانی برای تولید یا به‌روزرسانی مستندات فنی بر اساس منابع معتبر پروژه.

منابع ورودی می‌توانند شامل این موارد باشند:

  • Source Code
  • OpenAPI Schema
  • JSON Schema
  • Type Definition
  • Docstring
  • تست‌ها
  • README فعلی
  • Pull Request
  • Release Notes
  • ADR
  • مثال‌های واقعی
  • تنظیمات پروژه

خروجی‌ها:

  • API Reference
  • README
  • راهنمای نصب
  • Quickstart
  • Docstring
  • توضیح کلاس و تابع
  • مثال استفاده
  • Migration Guide
  • Changelog
  • راهنمای خطاها
  • مستندات معماری
  • راهنمای مشارکت

چرا OpenAPI منبع خوبی برای مستندسازی API است؟

OpenAPI یک رابط استاندارد و مستقل از زبان برای توصیف APIهای HTTP فراهم می‌کند. مصرف‌کننده می‌تواند بدون دسترسی به Source Code، Endpointها، ورودی‌ها و پاسخ‌ها را درک کند. این تعریف همچنین برای تولید مستندات، Client و تست قابل استفاده است. مشخصات رسمی OpenAPI

اطلاعات موجود در OpenAPI:

  • مسیر Endpoint
  • HTTP Method
  • Operation ID
  • پارامترهای Path و Query
  • Headerها
  • Request Body
  • Responseها
  • Schema داده‌ها
  • Tagها
  • توضیحات
  • وضعیت Deprecated
  • روش‌های احراز هویت تعریف‌شده

FastAPI می‌تواند Schema برنامه را با متد app.openapi() تولید کند. بنابراین برای پروژه آموزشی لازم نیست ابتدا Server را اجرا و سپس /openapi.json را دانلود کنیم.

AI چه چیزی به OpenAPI اضافه می‌کند؟

OpenAPI ساختار فنی را ارائه می‌دهد، اما ممکن است توضیح آن برای کاربر کافی نباشد.

Schema:

{
  "path": "/products/{product_id}",
  "method": "GET",
  "parameters": [
    {
      "name": "product_id",
      "in": "path",
      "required": true,
      "schema": {
        "type": "integer"
      }
    }
  ]
}

مستندات قابل‌استفاده:

این Endpoint اطلاعات یک محصول را با استفاده از شناسه عددی آن
برمی‌گرداند. اگر محصول وجود نداشته باشد، پاسخ 404 دریافت می‌شود.

نمونه درخواست:

curl http://localhost:8000/products/42

مدل نباید پاسخ 404 را اختراع کند. این پاسخ باید در OpenAPI یا Source Code مرتبط وجود داشته باشد.

انواع مستندات پروژه

API Reference

شرح دقیق هر Endpoint، پارامتر، Request و Response.

Tutorial

کاربر را برای رسیدن به یک نتیجه مشخص گام‌به‌گام هدایت می‌کند.

How-to Guide

راه‌حل یک مسئله محدود مانند «چگونه محصول جدید بسازیم؟».

Explanation

چرایی تصمیم‌ها و مفاهیم معماری را توضیح می‌دهد.

README

معرفی، نصب، اجرا و نقطه شروع پروژه.

Docstring

قرارداد محلی تابع، کلاس یا ماژول را توضیح می‌دهد.

یک مولد مستندات نباید همه این قالب‌ها را با یک پرامپت بسازد. هر نوع سند هدف، مخاطب و ساختار متفاوتی دارد.

چه چیزهایی را نباید از روی کد حدس زد؟

مدل نباید این اطلاعات را بدون منبع معتبر تولید کند:

  • هدف کسب‌وکار یک قابلیت
  • تضمین عملکرد
  • محدودیت نرخ درخواست
  • مدت نگهداری داده
  • سازگاری با نسخه‌های قبلی
  • زمان پاسخ تضمین‌شده
  • رفتار خطایی که در Schema نیست
  • متغیر محیطی تعریف‌نشده
  • Dependency نصب‌نشده
  • Endpoint موجودنبوده
  • پارامتر اختیاری یا اجباری برخلاف Schema
  • نمونه Response غیرمنطبق

برای اطلاعات نامشخص، خروجی باید سؤال یا هشدار تولید کند:

{
  "documentation_gaps": [
    "پاسخ 404 در Schema تعریف نشده است.",
    "واحد فیلد price مشخص نیست.",
    "محدوده مجاز page_size توضیح ندارد."
  ]
}

پروژه عملی این آموزش

یک API ساده محصولات می‌سازیم. سپس ابزار مستندسازی:

  1. app.openapi() را اجرا می‌کند.
  2. Operationهای API را استخراج می‌کند.
  3. Referenceهای Schema را Resolve می‌کند.
  4. Endpointها را جداگانه به مدل می‌فرستد.
  5. خروجی JSON را با Pydantic اعتبارسنجی می‌کند.
  6. مثال‌های curl و Python را بررسی می‌کند.
  7. مستندات Markdown می‌سازد.
  8. Hash قرارداد را ذخیره می‌کند.
  9. در اجرای بعدی تغییر OpenAPI را تشخیص می‌دهد.

ایجاد پروژه

mkdir ai-documentation-generator
cd ai-documentation-generator

python -m venv .venv

فعال‌سازی در Linux و macOS:

source .venv/bin/activate

فعال‌سازی در Windows:

.venv\Scripts\Activate.ps1

نصب وابستگی‌ها:

pip install \
  openai \
  python-dotenv \
  pydantic \
  fastapi \
  uvicorn \
  httpx

ساخت پوشه‌ها:

mkdir app doc_generator generated_docs state

فایل‌های زیر را ایجاد کنید:

app/__init__.py
app/main.py
doc_generator/__init__.py
doc_generator/schemas.py
doc_generator/openapi_parser.py
doc_generator/generator.py
doc_generator/validator.py
doc_generator/renderer.py
doc_generator/drift.py
generate_docs.py

تنظیم API درواره

فایل .env:

DARVAREH_API_KEY=YOUR_API_KEY
DARVAREH_MODEL=MODEL_ID_DARVAREH
DOCS_BASE_URL=http://localhost:8000

فایل .gitignore:

.env
.venv/
__pycache__/
.pytest_cache/
state/

برای دریافت API Key در درواره ثبت‌نام کنید. مدل مناسب تولید مستندات را از صفحه مدل‌های درواره انتخاب کنید.

ساخت API نمونه با FastAPI

فایل app/main.py:

from decimal import Decimal

from fastapi import (
    FastAPI,
    HTTPException,
    Query,
    status,
)
from pydantic import BaseModel, Field


app = FastAPI(
    title="Product Catalog API",
    version="1.0.0",
    description=(
        "API نمونه برای مدیریت کاتالوگ محصولات"
    ),
)


class ProductCreate(BaseModel):
    name: str = Field(
        min_length=2,
        max_length=120,
        description="نام قابل نمایش محصول",
        examples=["کیبورد مکانیکی"],
    )
    price: Decimal = Field(
        gt=0,
        description="قیمت محصول به ریال",
        examples=["3500000"],
    )
    is_active: bool = Field(
        default=True,
        description="وضعیت فعال‌بودن محصول",
    )


class ProductResponse(BaseModel):
    id: int
    name: str
    price: Decimal
    is_active: bool


PRODUCTS: dict[int, ProductResponse] = {
    1: ProductResponse(
        id=1,
        name="کیبورد مکانیکی",
        price=Decimal("3500000"),
        is_active=True,
    ),
    2: ProductResponse(
        id=2,
        name="ماوس بی‌سیم",
        price=Decimal("1200000"),
        is_active=True,
    ),
}


@app.get(
    "/products",
    response_model=list[ProductResponse],
    summary="دریافت فهرست محصولات",
    tags=["Products"],
)
def list_products(
    active_only: bool = Query(
        default=False,
        description=(
            "در صورت فعال‌بودن، فقط محصولات "
            "فعال برگردانده می‌شوند"
        ),
    ),
):
    products = list(PRODUCTS.values())

    if active_only:
        products = [
            product
            for product in products
            if product.is_active
        ]

    return products


@app.get(
    "/products/{product_id}",
    response_model=ProductResponse,
    summary="دریافت یک محصول",
    responses={
        404: {
            "description": (
                "محصول مورد نظر پیدا نشد"
            )
        }
    },
    tags=["Products"],
)
def get_product(
    product_id: int,
):
    product = PRODUCTS.get(product_id)

    if product is None:
        raise HTTPException(
            status_code=404,
            detail="Product not found",
        )

    return product


@app.post(
    "/products",
    response_model=ProductResponse,
    status_code=status.HTTP_201_CREATED,
    summary="ایجاد محصول",
    tags=["Products"],
)
def create_product(
    payload: ProductCreate,
):
    new_id = max(
        PRODUCTS.keys(),
        default=0,
    ) + 1

    product = ProductResponse(
        id=new_id,
        **payload.model_dump(),
    )

    PRODUCTS[new_id] = product

    return product

اجرای API:

uvicorn app.main:app --reload

مستندات خودکار FastAPI:

http://localhost:8000/docs

OpenAPI:

http://localhost:8000/openapi.json

بهبود Schema پیش از استفاده از AI

کیفیت مستندات تولیدشده به کیفیت OpenAPI وابسته است.

مقایسه کنید:

price: Decimal

با:

price: Decimal = Field(
    gt=0,
    description="قیمت محصول به ریال",
    examples=["3500000"],
)

نسخه دوم به مدل و کاربر اطلاعات مهمی درباره واحد، محدودیت و نمونه مقدار می‌دهد.

پیش از اضافه‌کردن AI، این فیلدها را کامل کنید:

  • summary
  • description
  • tags
  • response_model
  • responses
  • توضیح Field
  • Example
  • محدودیت Min و Max
  • Deprecated
  • Operation ID

AI نباید جای Schema ناقص را با حدس پر کند.

تعریف Schema خروجی مستندات

فایل doc_generator/schemas.py:

from pydantic import BaseModel, Field


class ParameterDoc(BaseModel):
    name: str
    location: str
    required: bool
    data_type: str
    description: str
    example: str | int | float | bool | None = None


class ResponseDoc(BaseModel):
    status_code: str
    description: str
    example: dict | list | str | None = None


class CodeExample(BaseModel):
    language: str
    title: str
    code: str
    explanation: str


class EndpointDocumentation(BaseModel):
    operation_id: str
    title: str
    method: str
    path: str
    purpose: str
    parameters: list[ParameterDoc] = Field(
        default_factory=list
    )
    request_body_description: str | None = None
    request_body_example: dict | list | None = None
    responses: list[ResponseDoc] = Field(
        default_factory=list
    )
    examples: list[CodeExample] = Field(
        default_factory=list
    )
    usage_notes: list[str] = Field(
        default_factory=list
    )
    documentation_gaps: list[str] = Field(
        default_factory=list
    )


class APIDocumentation(BaseModel):
    api_title: str
    api_version: str
    introduction: str
    quickstart: list[str]
    endpoints: list[EndpointDocumentation]

استخراج Operationها از OpenAPI

فایل doc_generator/openapi_parser.py:

from copy import deepcopy
from typing import Any


HTTP_METHODS = {
    "get",
    "post",
    "put",
    "patch",
    "delete",
    "options",
    "head",
}


def resolve_reference(
    reference: str,
    document: dict,
) -> Any:
    if not reference.startswith("#/"):
        raise ValueError(
            "Only local OpenAPI references "
            "are supported."
        )

    current: Any = document

    for part in reference[2:].split("/"):
        current = current[part]

    return deepcopy(current)


def resolve_schema(
    value: Any,
    document: dict,
) -> Any:
    if isinstance(value, list):
        return [
            resolve_schema(item, document)
            for item in value
        ]

    if not isinstance(value, dict):
        return value

    if "$ref" in value:
        resolved = resolve_reference(
            value["$ref"],
            document,
        )

        extra_fields = {
            key: item
            for key, item in value.items()
            if key != "$ref"
        }

        resolved.update(extra_fields)

        return resolve_schema(
            resolved,
            document,
        )

    return {
        key: resolve_schema(
            item,
            document,
        )
        for key, item in value.items()
    }


def extract_operations(
    openapi_document: dict,
) -> list[dict]:
    operations: list[dict] = []

    for path, path_item in (
        openapi_document
        .get("paths", {})
        .items()
    ):
        common_parameters = path_item.get(
            "parameters",
            [],
        )

        for method, operation in (
            path_item.items()
        ):
            if method.lower() not in (
                HTTP_METHODS
            ):
                continue

            operation_copy = deepcopy(
                operation
            )

            operation_copy["parameters"] = (
                common_parameters
                + operation_copy.get(
                    "parameters",
                    [],
                )
            )

            resolved = resolve_schema(
                operation_copy,
                openapi_document,
            )

            operations.append(
                {
                    "path": path,
                    "method": method.upper(),
                    "operation": resolved,
                }
            )

    operations.sort(
        key=lambda item: (
            item["path"],
            item["method"],
        )
    )

    return operations

این نسخه فقط Referenceهای محلی مانند زیر را Resolve می‌کند:

#/components/schemas/ProductCreate

برای OpenAPIهای چندفایلی باید Resolver کامل‌تری اضافه شود.

تولید مستندات هر Endpoint

فایل doc_generator/generator.py:

import json
import os

from dotenv import load_dotenv
from openai import OpenAI
from pydantic import ValidationError

from doc_generator.schemas import (
    EndpointDocumentation,
)


load_dotenv()

api_key = os.getenv("DARVAREH_API_KEY")
model = os.getenv("DARVAREH_MODEL")
base_url = os.getenv(
    "DOCS_BASE_URL",
    "http://localhost:8000",
)

if not api_key:
    raise RuntimeError(
        "DARVAREH_API_KEY is not configured."
    )

if not model:
    raise RuntimeError(
        "DARVAREH_MODEL is not configured."
    )

client = OpenAI(
    api_key=api_key,
    base_url="https://api.darvareh.ir/v1",
)


SYSTEM_PROMPT = """
تو یک Technical Writer ارشد هستی و برای توسعه‌دهندگان
مستندات API فارسی تولید می‌کنی.

منبع حقیقت فقط OpenAPI Operation ارائه‌شده است.

قواعد:
- Endpoint، پارامتر، پاسخ یا رفتار جدید اختراع نکن.
- required و optional را دقیقاً مطابق Schema بنویس.
- واحد و محدودیتی را که در Schema نیست حدس نزن.
- فقط Status Codeهای موجود را مستند کن.
- مثال‌ها باید با Schema سازگار باشند.
- مثال curl و Python تولید کن.
- مثال Python از کتابخانه requests استفاده کند.
- اگر اطلاعات لازم در Schema نیست، آن را در
  documentation_gaps ثبت کن.
- پاسخ فقط JSON معتبر باشد.
- Markdown خارج از JSON تولید نکن.
"""


OUTPUT_TEMPLATE = {
    "operation_id": "list_products_products_get",
    "title": "string",
    "method": "GET",
    "path": "/products",
    "purpose": "string",
    "parameters": [
        {
            "name": "active_only",
            "location": "query",
            "required": False,
            "data_type": "boolean",
            "description": "string",
            "example": True,
        }
    ],
    "request_body_description": None,
    "request_body_example": None,
    "responses": [
        {
            "status_code": "200",
            "description": "string",
            "example": [],
        }
    ],
    "examples": [
        {
            "language": "bash",
            "title": "نمونه curl",
            "code": "curl ...",
            "explanation": "string",
        },
        {
            "language": "python",
            "title": "نمونه Python",
            "code": "import requests...",
            "explanation": "string",
        },
    ],
    "usage_notes": [],
    "documentation_gaps": [],
}


def generate_endpoint_docs(
    operation_data: dict,
) -> EndpointDocumentation:
    payload = {
        "base_url": base_url,
        "path": operation_data["path"],
        "method": operation_data["method"],
        "openapi_operation": (
            operation_data["operation"]
        ),
    }

    response = client.chat.completions.create(
        model=model,
        temperature=0.1,
        messages=[
            {
                "role": "system",
                "content": SYSTEM_PROMPT,
            },
            {
                "role": "user",
                "content": (
                    "برای Operation زیر مستندات بساز:\n\n"
                    + json.dumps(
                        payload,
                        ensure_ascii=False,
                        indent=2,
                    )
                    + "\n\nقالب خروجی:\n"
                    + json.dumps(
                        OUTPUT_TEMPLATE,
                        ensure_ascii=False,
                        indent=2,
                    )
                ),
            },
        ],
    )

    raw_output = (
        response.choices[0]
        .message.content
    )

    if not raw_output:
        raise RuntimeError(
            "The model returned empty docs."
        )

    try:
        parsed = json.loads(raw_output)
    except json.JSONDecodeError as error:
        raise RuntimeError(
            f"Invalid JSON from model: "
            f"{error}"
        ) from error

    try:
        return (
            EndpointDocumentation
            .model_validate(parsed)
        )
    except ValidationError as error:
        raise RuntimeError(
            f"Documentation validation "
            f"failed: {error}"
        ) from error

اعتبارسنجی مستندات با OpenAPI

مدل ممکن است Method، Path یا پارامتر را اشتباه بازگرداند. خروجی باید با Operation اصلی تطبیق داده شود.

فایل doc_generator/validator.py:

from doc_generator.schemas import (
    EndpointDocumentation,
)


class DocumentationValidationError(
    ValueError
):
    pass


def validate_endpoint_docs(
    docs: EndpointDocumentation,
    operation_data: dict,
) -> None:
    expected_path = operation_data["path"]
    expected_method = operation_data["method"]

    if docs.path != expected_path:
        raise DocumentationValidationError(
            f"Path mismatch: {docs.path} "
            f"!= {expected_path}"
        )

    if docs.method != expected_method:
        raise DocumentationValidationError(
            f"Method mismatch: {docs.method} "
            f"!= {expected_method}"
        )

    operation = operation_data["operation"]

    expected_parameters = {
        (
            parameter.get("name"),
            parameter.get("in"),
        )
        for parameter in operation.get(
            "parameters",
            []
        )
    }

    documented_parameters = {
        (
            parameter.name,
            parameter.location,
        )
        for parameter in docs.parameters
    }

    unknown_parameters = (
        documented_parameters
        - expected_parameters
    )

    if unknown_parameters:
        raise DocumentationValidationError(
            "Unknown parameters documented: "
            + str(
                sorted(unknown_parameters)
            )
        )

    expected_status_codes = {
        str(status_code)
        for status_code in operation.get(
            "responses",
            {}
        )
    }

    documented_status_codes = {
        response.status_code
        for response in docs.responses
    }

    unknown_status_codes = (
        documented_status_codes
        - expected_status_codes
    )

    if unknown_status_codes:
        raise DocumentationValidationError(
            "Unknown response codes: "
            + str(
                sorted(unknown_status_codes)
            )
        )

در نسخه کامل‌تر باید این موارد نیز بررسی شوند:

  • تمام پارامترهای Required مستند شده باشند.
  • نوع پارامتر با Schema سازگار باشد.
  • Request Example با JSON Schema تطبیق داشته باشد.
  • Response Example معتبر باشد.
  • URL مثال با Path Parameterهای واقعی ساخته شده باشد.

اعتبارسنجی مثال Python

مثال Python را می‌توان حداقل از نظر Syntax بررسی کرد:

def validate_python_example(
    code: str,
) -> None:
    try:
        compile(
            code,
            "<documentation-example>",
            "exec",
        )
    except SyntaxError as error:
        raise ValueError(
            f"Invalid Python example: {error}"
        ) from error

برای پیدا کردن مثال Python:

for example in docs.examples:
    if example.language.lower() == "python":
        validate_python_example(
            example.code
        )

Syntax معتبر تضمین نمی‌کند درخواست اجرا می‌شود. بهترین روش، اجرای مثال‌ها علیه یک محیط آزمایشی کنترل‌شده است.

ساخت Markdown

فایل doc_generator/renderer.py:

from pathlib import Path

from doc_generator.schemas import (
    EndpointDocumentation,
)


def render_endpoint_markdown(
    docs: EndpointDocumentation,
) -> str:
    lines = [
        f"# {docs.title}",
        "",
        f"`{docs.method} {docs.path}`",
        "",
        docs.purpose,
        "",
        "## پارامترها",
        "",
    ]

    if docs.parameters:
        lines.extend(
            [
                "| نام | محل | نوع | اجباری | توضیح |",
                "|---|---|---|---|---|",
            ]
        )

        for parameter in docs.parameters:
            required = (
                "بله"
                if parameter.required
                else "خیر"
            )

            description = (
                parameter.description
                .replace("|", "\\|")
            )

            lines.append(
                f"| `{parameter.name}` "
                f"| `{parameter.location}` "
                f"| `{parameter.data_type}` "
                f"| {required} "
                f"| {description} |"
            )
    else:
        lines.append(
            "این Endpoint پارامتر مستقیمی ندارد."
        )

    if docs.request_body_description:
        lines.extend(
            [
                "",
                "## بدنه درخواست",
                "",
                docs.request_body_description,
                "",
            ]
        )

        if docs.request_body_example is not None:
            import json

            lines.extend(
                [
                    "```json",
                    json.dumps(
                        docs.request_body_example,
                        ensure_ascii=False,
                        indent=2,
                    ),
                    "```",
                ]
            )

    lines.extend(
        [
            "",
            "## پاسخ‌ها",
            "",
        ]
    )

    for response in docs.responses:
        lines.extend(
            [
                f"### HTTP {response.status_code}",
                "",
                response.description,
                "",
            ]
        )

        if response.example is not None:
            import json

            if isinstance(
                response.example,
                (dict, list),
            ):
                rendered_example = json.dumps(
                    response.example,
                    ensure_ascii=False,
                    indent=2,
                )
                language = "json"
            else:
                rendered_example = str(
                    response.example
                )
                language = "text"

            lines.extend(
                [
                    f"```{language}",
                    rendered_example,
                    "```",
                    "",
                ]
            )

    lines.extend(
        [
            "## مثال‌ها",
            "",
        ]
    )

    for example in docs.examples:
        lines.extend(
            [
                f"### {example.title}",
                "",
                example.explanation,
                "",
                f"```{example.language}",
                example.code,
                "```",
                "",
            ]
        )

    if docs.usage_notes:
        lines.extend(
            [
                "## نکات استفاده",
                "",
            ]
        )

        for note in docs.usage_notes:
            lines.append(f"- {note}")

        lines.append("")

    if docs.documentation_gaps:
        lines.extend(
            [
                "## موارد نیازمند تکمیل",
                "",
            ]
        )

        for gap in docs.documentation_gaps:
            lines.append(f"- {gap}")

        lines.append("")

    return "\n".join(lines)


def save_endpoint_docs(
    docs: EndpointDocumentation,
    output_directory: Path,
) -> Path:
    safe_operation_id = "".join(
        character
        if character.isalnum()
        or character in {"-", "_"}
        else "-"
        for character in docs.operation_id
    )

    output_path = (
        output_directory
        / f"{safe_operation_id}.md"
    )

    output_path.parent.mkdir(
        parents=True,
        exist_ok=True,
    )

    output_path.write_text(
        render_endpoint_markdown(docs),
        encoding="utf-8",
    )

    return output_path

در Markdown تولیدشده از Divider خطی استفاده نشده است.

تشخیص تغییر یا Documentation Drift

اگر OpenAPI تغییر کند ولی مستندات دوباره تولید نشوند، Drift ایجاد می‌شود.

فایل doc_generator/drift.py:

import hashlib
import json
from pathlib import Path


def calculate_contract_hash(
    openapi_document: dict,
) -> str:
    normalized = json.dumps(
        openapi_document,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
    )

    return hashlib.sha256(
        normalized.encode("utf-8")
    ).hexdigest()


def read_previous_hash(
    state_path: Path,
) -> str | None:
    if not state_path.exists():
        return None

    return state_path.read_text(
        encoding="utf-8"
    ).strip() or None


def write_contract_hash(
    state_path: Path,
    contract_hash: str,
) -> None:
    state_path.parent.mkdir(
        parents=True,
        exist_ok=True,
    )

    state_path.write_text(
        contract_hash,
        encoding="utf-8",
    )

این روش فقط مشخص می‌کند Schema تغییر کرده است. برای گزارش دقیق تغییر باید نسخه قبلی OpenAPI نیز ذخیره و با نسخه جدید مقایسه شود.

برنامه اصلی تولید مستندات

فایل generate_docs.py:

import json
from pathlib import Path

from app.main import app
from doc_generator.drift import (
    calculate_contract_hash,
    read_previous_hash,
    write_contract_hash,
)
from doc_generator.generator import (
    generate_endpoint_docs,
)
from doc_generator.openapi_parser import (
    extract_operations,
)
from doc_generator.renderer import (
    save_endpoint_docs,
)
from doc_generator.validator import (
    validate_endpoint_docs,
    validate_python_example,
)


OUTPUT_DIRECTORY = Path(
    "generated_docs/api"
)

STATE_PATH = Path(
    "state/openapi.sha256"
)

OPENAPI_SNAPSHOT_PATH = Path(
    "generated_docs/openapi.json"
)


def main():
    openapi_document = app.openapi()

    current_hash = calculate_contract_hash(
        openapi_document
    )

    previous_hash = read_previous_hash(
        STATE_PATH
    )

    if previous_hash == current_hash:
        print(
            "OpenAPI contract has not changed."
        )
    else:
        print(
            "OpenAPI contract changed; "
            "regenerating documentation."
        )

    operations = extract_operations(
        openapi_document
    )

    generated_paths = []

    for index, operation in enumerate(
        operations,
        start=1,
    ):
        print(
            f"Generating docs "
            f"{index}/{len(operations)}: "
            f"{operation['method']} "
            f"{operation['path']}"
        )

        docs = generate_endpoint_docs(
            operation
        )

        validate_endpoint_docs(
            docs,
            operation,
        )

        for example in docs.examples:
            if (
                example.language.lower()
                == "python"
            ):
                validate_python_example(
                    example.code
                )

        output_path = save_endpoint_docs(
            docs,
            OUTPUT_DIRECTORY,
        )

        generated_paths.append(
            output_path
        )

    OPENAPI_SNAPSHOT_PATH.parent.mkdir(
        parents=True,
        exist_ok=True,
    )

    OPENAPI_SNAPSHOT_PATH.write_text(
        json.dumps(
            openapi_document,
            ensure_ascii=False,
            indent=2,
        ),
        encoding="utf-8",
    )

    write_contract_hash(
        STATE_PATH,
        current_hash,
    )

    print(
        f"Generated {len(generated_paths)} "
        f"documentation files."
    )


if __name__ == "__main__":
    main()

اجرای ابزار:

python generate_docs.py

ساخت صفحه فهرست API

برای دسترسی آسان‌تر، یک فایل Index بسازید:

def build_index(
    docs_list,
    output_path: Path,
):
    lines = [
        "# مستندات API محصولات",
        "",
        "این مستندات از روی قرارداد OpenAPI "
        "و پس از اعتبارسنجی تولید شده‌اند.",
        "",
        "## Endpointها",
        "",
    ]

    for docs, file_path in docs_list:
        lines.append(
            f"- [{docs.method} {docs.path}]"
            f"({file_path.name}) — "
            f"{docs.title}"
        )

    output_path.write_text(
        "\n".join(lines),
        encoding="utf-8",
    )

نمونه مستندات Endpoint

# دریافت یک محصول

`GET /products/{product_id}`

این Endpoint اطلاعات یک محصول را با استفاده از شناسه عددی آن برمی‌گرداند.

## پارامترها

| نام | محل | نوع | اجباری | توضیح |
|---|---|---|---|---|
| `product_id` | `path` | `integer` | بله | شناسه محصول مورد نظر |

## پاسخ‌ها

### HTTP 200

اطلاعات محصول با موفقیت برگردانده شد.

### HTTP 404

محصول مورد نظر پیدا نشد.

## مثال‌ها

### نمونه curl

```bash
curl --request GET \
  "http://localhost:8000/products/1"

نمونه Python

import requests

response = requests.get(
    "http://localhost:8000/products/1",
    timeout=10,
)

response.raise_for_status()
print(response.json())

# تولید Docstring با هوش مصنوعی

برای تولید Docstring بهتر است امضای تابع، Typeها، Exceptionها، Specification و تست‌های آن را به مدل بدهید.

پرامپت:

```text
برای تابع زیر Docstring بنویس.

قالب:
Google Style

منابع:
- Signature
- Source Code
- Specification
- Tests

قواعد:
- نحوه کار داخلی را خط‌به‌خط توضیح نده.
- قرارداد عمومی تابع را مستند کن.
- پارامترها، خروجی و Exceptionهای قابل اثبات را بنویس.
- Type را برخلاف Signature تغییر نده.
- Example فقط در صورت امکان از API عمومی بساز.
- رفتار یا Exception جدید اختراع نکن.
- اگر رفتار مبهم است، آن را جداگانه گزارش کن.

کد:
[SOURCE]

Specification:
[SPEC]

Tests:
[TESTS]

نمونه Docstring:

def calculate_cart(
    items: list[CartItem],
    discount_rate: Decimal = Decimal("0"),
    tax_rate: Decimal = Decimal("0"),
) -> CartResult:
    """Calculate totals for a shopping cart.

    Discount is applied to the subtotal before tax. Monetary
    values are rounded to two decimal places using ROUND_HALF_UP.

    Args:
        items: Cart items with non-negative prices and quantities.
        discount_rate: Discount rate between zero and one.
        tax_rate: Tax rate between zero and one.

    Returns:
        Calculated subtotal, discount, taxable amount, tax, and total.

    Raises:
        ValueError: If discount_rate or tax_rate is outside [0, 1].
        InvalidCartItemError: If an item has a negative price or quantity.
    """

هر Exception مستندشده باید در کد یا قرارداد وجود داشته باشد.

تولید README با AI

برای ساخت README این Context را فراهم کنید:

  • فایل Dependencyها
  • دستور اجرای واقعی
  • ساختار پروژه
  • Entry Point
  • متغیرهای محیطی
  • Dockerfile
  • Compose
  • تست‌ها
  • نمونه درخواست
  • License موجود
  • نسخه Runtime

ساختار پیشنهادی README:

1. معرفی
2. قابلیت‌ها
3. پیش‌نیازها
4. نصب
5. تنظیم متغیرهای محیطی
6. اجرای محلی
7. اجرای تست
8. نمونه استفاده
9. ساختار پروژه
10. توسعه
11. محدودیت‌ها

مدل نباید دستور نصب یا متغیر محیطی را فقط بر اساس الگوهای رایج حدس بزند.

تولید Changelog از Git Diff

ورودی:

  • Git Diff
  • عنوان PRها
  • Labelها
  • نسخه قبلی و جدید
  • نوع مخاطب
  • تغییرات قرارداد

خروجی:

{
  "added": [],
  "changed": [],
  "fixed": [],
  "deprecated": [],
  "removed": [],
  "migration_notes": []
}

پرامپت:

از تغییرات زیر Changelog کاربرمحور تولید کن.

قواعد:
- Refactor داخلی بدون اثر کاربر را حذف کن.
- تغییر رفتار عمومی را توضیح بده.
- Breaking Change را فقط با شاهد مشخص اعلام کن.
- Issue یا PR جدید اختراع نکن.
- جزئیات پیاده‌سازی غیرضروری را ننویس.

تولید Migration Guide

اگر Contract قدیمی و جدید را دارید، مدل می‌تواند تفاوت‌ها را توضیح دهد:

نسخه قبلی:
[OLD OPENAPI]

نسخه جدید:
[NEW OPENAPI]

خروجی:

  • Endpointهای جدید
  • Endpointهای حذف‌شده
  • پارامترهای تغییرکرده
  • تغییر Required
  • تغییر Response
  • تغییر نام فیلد
  • مراحل مهاجرت
  • مثال قبل و بعد
  • ابهام‌های نیازمند بررسی

تشخیص تغییر باید ابتدا با ابزار مقایسه Schema انجام شود. مدل نتیجه قطعی Diff را توضیح دهد، نه اینکه خودش تنها مرجع تشخیص باشد.

تولید مستندات از Source Code بدون OpenAPI

برای یک کتابخانه یا ماژول داخلی می‌توان AST را Parse کرد.

نمونه استخراج توابع Python:

import ast
from pathlib import Path


def extract_public_functions(
    file_path: Path,
) -> list[dict]:
    source = file_path.read_text(
        encoding="utf-8"
    )

    tree = ast.parse(source)

    functions = []

    for node in tree.body:
        if not isinstance(
            node,
            (
                ast.FunctionDef,
                ast.AsyncFunctionDef,
            ),
        ):
            continue

        if node.name.startswith("_"):
            continue

        functions.append(
            {
                "name": node.name,
                "line": node.lineno,
                "end_line": node.end_lineno,
                "docstring": (
                    ast.get_docstring(node)
                ),
                "signature_source": (
                    ast.get_source_segment(
                        source,
                        node,
                    )
                ),
            }
        )

    return functions

بهتر است فقط امضا و بدنه تابع مرتبط ارسال شود، نه کل Repository.

تشخیص مستندات قدیمی

چند روش:

Hash قرارداد

اگر OpenAPI تغییر کرد، مستندات باید دوباره ساخته شوند.

مقایسه Symbolها

اگر تابع یا کلاس حذف شده ولی هنوز در مستندات لینک دارد، Drift وجود دارد.

اجرای مثال‌ها

مثال‌های Python و Shell در CI اجرا شوند.

بررسی لینک‌ها

لینک‌های داخلی و Anchorها بررسی شوند.

تطبیق پارامترها

نام، Type و Required بودن پارامتر مستند با Schema مقایسه شود.

Snapshot مستندات

مستندات تولیدشده در Git ذخیره شوند تا تغییرات آن در Pull Request قابل بررسی باشد.

Docs-as-Code در CI

جریان پیشنهادی:

Code Change
    ↓
Generate OpenAPI
    ↓
Compare Contract Hash
    ↓
Generate Documentation Draft
    ↓
Validate Parameters and Examples
    ↓
Run Documentation Tests
    ↓
Show Markdown Diff in Pull Request
    ↓
Human Review
    ↓
Publish

اگر OpenAPI تغییر کرده اما مستندات تغییر نکرده‌اند، CI می‌تواند هشدار دهد.

تست مثال‌های مستندات

مثال‌ها بخشی از محصول‌اند و باید تست شوند.

برای کدهای Python:

  1. Syntax را با compile بررسی کنید.
  2. Importها را بررسی کنید.
  3. در محیط آزمایشی اجرا کنید.
  4. Timeout داشته باشید.
  5. Response را با Schema مقایسه کنید.

برای curl:

  1. URL درست باشد.
  2. HTTP Method با Operation یکسان باشد.
  3. Path Parameter جایگزین شده باشد.
  4. Request Body با Schema سازگار باشد.
  5. Headerهای ضروری وجود داشته باشند.

مثال نباید مستقیماً روی محیط Production اجرا شود.

تولید مثال از Schema در کد

برای فیلدهای ساده بهتر است مثال با کد قطعی ساخته شود، نه AI.

def example_from_schema(
    schema: dict,
):
    if "example" in schema:
        return schema["example"]

    if "default" in schema:
        return schema["default"]

    schema_type = schema.get("type")

    if schema_type == "string":
        return "string"

    if schema_type == "integer":
        return 1

    if schema_type == "number":
        return 1.0

    if schema_type == "boolean":
        return True

    if schema_type == "array":
        return [
            example_from_schema(
                schema.get("items", {})
            )
        ]

    if schema_type == "object":
        properties = schema.get(
            "properties",
            {}
        )

        required = set(
            schema.get("required", [])
        )

        return {
            name: example_from_schema(
                property_schema
            )
            for name, property_schema
            in properties.items()
            if (
                name in required
                or "example"
                in property_schema
                or "default"
                in property_schema
            )
        }

    return None

AI می‌تواند توضیح مثال را بنویسد، اما ساختار اولیه بهتر است از Schema استخراج شود.

مستندسازی خطاها

هر خطا باید حداقل این اطلاعات را داشته باشد:

  • Status Code
  • شرایط وقوع
  • ساختار Response
  • کد خطای داخلی در صورت وجود
  • اقدام پیشنهادی کاربر
  • مثال معتبر

نمونه:

{
  "status_code": 404,
  "error": {
    "code": "PRODUCT_NOT_FOUND",
    "message": "Product not found"
  }
}

اگر API فقط detail برمی‌گرداند، مستندات نباید ساختار code را اختراع کند.

معماری مستندات چندزبانه

منبع حقیقت باید زبان‌خنثی بماند:

{
  "operation_id": "get_product",
  "method": "GET",
  "path": "/products/{product_id}",
  "parameters": [],
  "responses": []
}

سپس Renderer یا مدل نسخه فارسی و انگلیسی را تولید کند.

نکته مهم:

  • نام فیلدها ترجمه نشوند.
  • کد و URL ثابت بمانند.
  • JSON Example در همه زبان‌ها یکسان باشد.
  • فقط توضیحات انسانی ترجمه شوند.
  • هر دو نسخه از یک Contract Hash ساخته شوند.

مدیریت هزینه تولید مستندات

فقط Operationهای تغییرکرده

اگر Hash یک Operation تغییر نکرده است، مستندات آن دوباره ساخته نشود.

کلید Cache:

operation_hash +
model_id +
prompt_version +
language +
template_version

تولید قطعی بخش‌های ساختاری

جدول پارامترها، Status Codeها و Schemaها را با کد بسازید. از AI برای توضیح، Tutorial و نکات استفاده کنید.

پردازش جداگانه Endpointها

در صورت خطای یک Endpoint، کل تولید مستندات متوقف نشود.

مدل متناسب با وظیفه

خلاصه و بازنویسی ساده ممکن است به مدل بسیار قوی نیاز نداشته باشد.

انتخاب مدل مناسب

ویژگی‌های مهم:

  • پیروی دقیق از Schema
  • تولید JSON معتبر
  • کیفیت نگارش فارسی
  • توانایی فهم کد
  • حفظ نام‌های فنی
  • ساخت مثال سازگار
  • Context Window مناسب
  • هزینه و سرعت

برای مستندات کوتاه Endpoint می‌توان از مدل سریع‌تر استفاده کرد. برای توضیح معماری یا Migration Guide چندفایلی، مدل قوی‌تر ممکن است نتیجه بهتری ارائه دهد.

فهرست و اطلاعات به‌روز مدل‌ها در صفحه مدل‌های درواره قرار دارد.

ارزیابی Documentation Generator

یک Dataset مرجع بسازید که شامل موارد زیر باشد:

  • Endpoint بدون پارامتر
  • Path Parameter
  • Query Parameter اختیاری
  • Request Body
  • چند Response
  • Schema تو در تو
  • Enum
  • Pagination
  • Endpoint Deprecated
  • اطلاعات ناقص
  • نام‌های فنی انگلیسی در متن فارسی

معیارها:

معیارتوضیح
Contract Accuracyتطابق با OpenAPI
Parameter Coverageپوشش پارامترها
Hallucination Rateاطلاعات اختراع‌شده
Example Validityمعتبر بودن مثال‌ها
Code Syntax Rateدرصد مثال‌های قابل Parse
Gap Detectionتشخیص اطلاعات ناقص
Readabilityوضوح برای مخاطب
Drift Detectionتشخیص تغییر قرارداد
Human Edit Rateمیزان ویرایش لازم
Cost per Operationهزینه هر Endpoint

اشتباهات رایج

تولید مستندات فقط از روی Source Code

در API، OpenAPI و تست قرارداد منابع دقیق‌تری هستند.

پذیرش مثال بدون اجرا

مثال اشتباه اعتماد کاربر را کاهش می‌دهد.

حدس‌زدن اطلاعات ناقص

اطلاعات ناقص باید در documentation_gaps ثبت شود.

تولید همه بخش‌ها با مدل

جدول پارامتر و Status Code را می‌توان به‌صورت قطعی از Schema ساخت.

نداشتن Version و Hash

بدون تشخیص Drift، مستندات دوباره قدیمی می‌شوند.

ترجمه نام فیلدها

فیلدهای JSON، Endpointها و نام پارامترها نباید ترجمه شوند.

مستندسازی جزئیات داخلی

مستندات عمومی باید Contract و روش استفاده را توضیح دهند، نه تمام پیاده‌سازی داخلی را.

انتشار خودکار بدون Review

تغییر مستندات باید همراه تغییر کد بررسی شود.

برنامه پیاده‌سازی در تیم

مرحله اول: کامل‌کردن OpenAPI

  • Summary
  • Description
  • Field Description
  • Responseها
  • Exampleها

مرحله دوم: تولید Draft

برای هر Endpoint یک فایل Markdown ساخته شود.

مرحله سوم: اعتبارسنجی

Path، Method، Parameter، Response و Syntax مثال‌ها بررسی شوند.

مرحله چهارم: Drift Detection

Hash قرارداد در CI محاسبه شود.

مرحله پنجم: اجرای مثال‌ها

Quickstartها روی محیط آزمایشی اجرا شوند.

مرحله ششم: انتشار

فقط مستندات تأییدشده منتشر شوند.

چک‌لیست مستندات تولیدشده

  • Path و Method درست هستند.
  • تمام پارامترهای اجباری ذکر شده‌اند.
  • پارامتر اختیاری اجباری معرفی نشده است.
  • نوع داده با Schema سازگار است.
  • واحد مبلغ یا زمان مشخص است.
  • Request Example معتبر است.
  • Response Example با Schema تطبیق دارد.
  • فقط Status Codeهای واقعی مستند شده‌اند.
  • مثال Python از نظر Syntax معتبر است.
  • مثال curl قابل اجرا است.
  • نام فیلدها ترجمه نشده‌اند.
  • اطلاعات ناموجود حدس زده نشده‌اند.
  • ابهام‌ها ثبت شده‌اند.
  • Contract Hash ذخیره شده است.
  • تغییرات در Code Review دیده می‌شوند.

پرسش‌های متداول

آیا هوش مصنوعی می‌تواند کد را مستند کند؟

بله. AI می‌تواند برای تابع، کلاس، ماژول، API و معماری پیش‌نویس مستندات بسازد. برای نتیجه دقیق باید Source Code، Typeها، Specification و تست‌ها را دریافت کند.

چگونه با AI مستندات API بسازیم؟

OpenAPI Schema را استخراج و هر Operation را جداگانه به مدل ارسال کنید. خروجی باید ساختاریافته باشد و Path، Method، پارامترها و Responseها دوباره با Schema تطبیق داده شوند.

آیا می‌توان README را خودکار تولید کرد؟

بله، اما دستور نصب، متغیرهای محیطی و Entry Point باید از فایل‌های واقعی پروژه استخراج شوند. مدل نباید آن‌ها را حدس بزند.

چگونه از قدیمی‌شدن مستندات جلوگیری کنیم؟

OpenAPI یا Source Contract را Hash کنید و در CI با نسخه قبلی مقایسه کنید. اگر قرارداد تغییر کرده باشد، مستندات باید بازتولید و Review شوند.

آیا مثال‌های تولیدشده توسط AI قابل اعتمادند؟

بدون تست خیر. کد Python را Parse و اجرا کنید و درخواست‌های API را روی محیط آزمایشی با Schema پاسخ مقایسه کنید.

آیا AI جای Swagger UI را می‌گیرد؟

خیر. Swagger UI یا ابزار مشابه Reference ساختاری API را نمایش می‌دهد. AI می‌تواند توضیح فارسی، Quickstart، Tutorial و مثال‌های کاربردی‌تر تولید کند.

برای مستندسازی از کدام مدل استفاده کنیم؟

مدل باید در پیروی از Schema، تولید کد و نگارش فارسی عملکرد مناسبی داشته باشد. مدل‌ها و قیمت به‌روز را در صفحه مدل‌های درواره ببینید.

API درواره چگونه متصل می‌شود؟

در Backend، base_url را روی https://api.darvareh.ir/v1 قرار دهید و OpenAPI Operation را همراه پرامپت کنترل‌شده برای مدل ارسال کنید.

جمع‌بندی

هوش مصنوعی می‌تواند زمان تولید مستندات را کاهش دهد، اما نباید منبع حقیقت پروژه باشد. Source Code، OpenAPI، JSON Schema و تست‌های قرارداد باید رفتار واقعی را تعیین کنند و مدل آن‌ها را به توضیح قابل فهم تبدیل کند.

در پروژه این مقاله یک AI Documentation Generator ساختیم که OpenAPI برنامه FastAPI را استخراج می‌کند، Endpointها را جداگانه به API درواره می‌فرستد، خروجی را با Pydantic اعتبارسنجی می‌کند و مستندات Markdown فارسی همراه مثال curl و Python می‌سازد.

همچنین با ذخیره Hash قرارداد، تغییر API و احتمال قدیمی‌شدن مستندات را تشخیص دادیم. این معماری را می‌توان به تولید README، Docstring، Changelog و Migration Guide گسترش داد.

برای شروع، در درواره ثبت‌نام و API Key دریافت کنید. سپس مدل مناسب را از صفحه مدل‌های درواره انتخاب کرده و ابزار را ابتدا روی چند Endpoint دارای OpenAPI کامل آزمایش کنید.

مقالات مرتبط

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی با ترکیب گردش‌کارهای خودکار و مدل‌های هوش مصنوعی، پردازش متن، دسته‌بندی، استخراج اطلاعات و تصمیم‌های پیشنهادی را خودکار می‌کند. در این راهنما، معماری و ساخت نمونه عملی آن با API درواره را می‌آموزید.

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce شیوه‌ای جدید برای خرید اینترنتی است که در آن ایجنت هوش مصنوعی می‌تواند نیاز کاربر را بفهمد، محصولات را جست‌وجو و مقایسه کند و فرایند خرید را پیش ببرد. در این راهنما با معماری، UCP، ACP و پیاده‌سازی آن با API درواره آشنا می‌شوید.