0Pricing
AI Agents · Lección

Versionado y compatibilidad de herramientas

Versionado semántico de herramientas, compatibilidad con versiones anteriores y patrones de obsolescencia.

Versionado y compatibilidad de herramientas es una lección gratuita de AI Agents en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Agents, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Agents incluye 4 lecciones en total.

Por qué es importante versionar las herramientas

Cuando cambia la interfaz de una herramienta —se renombra un parámetro, se añade un campo obligatorio o cambia la estructura de un valor devuelto—, los agentes que dependen de la interfaz anterior fallan silenciosamente. El versionado con reglas semánticas y avisos de obsolescencia evita este problema.

Versionado semántico de herramientas

Siga el versionado semántico: MAJOR.MINOR.PATCH. Incremente MAJOR cuando haya cambios incompatibles (parámetros eliminados, tipos de parámetros modificados o cambios en la estructura de los valores devueltos). Incremente MINOR al añadir elementos compatibles con versiones anteriores e incremente PATCH para corregir errores que no afecten a la interfaz.

VERSIONING_RULES = {
    'major_bump': [
        'Removed a required or optional parameter',
        'Renamed an existing parameter',
        'Changed parameter type (e.g., string -> object)',
        'Changed response field names or types',
        'Removed a response field',
        'Changed error code values'
    ],
    'minor_bump': [
        'Added an optional parameter',
        'Added a new response field',
        'Added a new tool to the plugin'
    ],
    'patch_bump': [
        'Fixed a bug without interface change',
        'Improved error messages',
        'Performance improvement',
        'Updated documentation'
    ]
}

for bump_type, examples in VERSIONING_RULES.items():
    print(f'{bump_type}:')
    for ex in examples[:2]:
        print(f'  - {ex}')

Análisis y comparación de versiones

Implemente utilidades de comparación de versiones para comprobar la compatibilidad. Un agente puede declarar en su configuración la versión mínima de herramienta que necesita, y el registro rechaza las herramientas que no cumplen el requisito.

from functools import total_ordering

@total_ordering
class Version:
    def __init__(self, version_str: str):
        parts = version_str.strip().split('.')
        if len(parts) != 3 or not all(p.isdigit() for p in parts):
            raise ValueError(f'Invalid version: {version_str}')
        self.major, self.minor, self.patch = map(int, parts)

    def __str__(self):
        return f'{self.major}.{self.minor}.{self.patch}'

    def __eq__(self, other):
        return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)

    def __lt__(self, other):
        return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)

    def is_compatible_with(self, required: 'Version') -> bool:
        """Compatible if same major version and >= required minor.patch"""
        return self.major == required.major and self >= required

v = Version('2.3.1')
required = Version('2.1.0')
print(f'{v} compatible with {required}: {v.is_compatible_with(required)}')
print(f'Is newer: {v > required}')

Fijación de versiones en la configuración del agente

Las configuraciones de los agentes deben fijar la versión mínima necesaria para cada dependencia de herramienta. Esto evita que un agente utilice accidentalmente una versión más reciente e incompatible de una herramienta cuando se actualice el plugin.

AGENT_TOOL_REQUIREMENTS = {
    'get_weather': '>=1.2.0',
    'search_knowledge_base': '>=3.0.0',
    'send_email': '>=2.1.0,<3.0.0'  # exclude major-version bump
}

def parse_version_constraint(constraint: str) -> list:
    """
    Parses constraints like '>=1.2.0,<3.0.0'
    Returns list of (operator, Version) tuples
    """
    ops = {'>=': lambda a, b: a >= b, '>': lambda a, b: a > b,
           '<=': lambda a, b: a <= b, '<': lambda a, b: a < b,
           '==': lambda a, b: a == b}
    parts = [p.strip() for p in constraint.split(',')]
    parsed = []
    for part in parts:
        for op_str, op_fn in ops.items():
            if part.startswith(op_str):
                parsed.append((op_fn, Version(part[len(op_str):])))
                break
    return parsed

def satisfies_constraint(tool_version: str, constraint: str) -> bool:
    v = Version(tool_version)
    rules = parse_version_constraint(constraint)
    return all(op(v, required) for op, required in rules)

print(satisfies_constraint('2.3.0', '>=2.1.0,<3.0.0'))  # True
print(satisfies_constraint('3.0.0', '>=2.1.0,<3.0.0'))  # False

Avisos de obsolescencia

Al eliminar o cambiar el nombre de un parámetro, márquelo primero como obsoleto en una versión MINOR: manténgalo operativo, pero emita un aviso de obsolescencia en la respuesta. Elimínelo únicamente en la siguiente versión MAJOR. Esto da tiempo a los desarrolladores de agentes para actualizarse.

import warnings

def execute_get_weather(params: dict) -> dict:
    # Handle deprecated parameter 'temp_unit' -> replaced by 'units'
    if 'temp_unit' in params:
        warnings.warn(
            'Parameter temp_unit is deprecated since v1.3.0. '
            'Use units instead. Will be removed in v2.0.0.',
            DeprecationWarning,
            stacklevel=2
        )
        params = dict(params)
        params['units'] = params.pop('temp_unit')

    # Include deprecation notice in response
    result = _fetch_weather(params['city'], params.get('units', 'celsius'))
    if 'temp_unit' in params:
        result['_deprecation_warnings'] = [
            'temp_unit deprecated; use units'
        ]
    return result

# When calling deprecated param:
with warnings.catch_warnings(record=True) as w:
    warnings.simplefilter('always')
    # result = execute_get_weather({'city': 'London', 'temp_unit': 'celsius'})
    print('Deprecation warnings would be captured here')

Guías de migración

Con cada incremento de versión MAJOR, publique una guía de migración que muestre exactamente qué ha cambiado e incluya ejemplos de código antes y después. Sin una guía de migración, los desarrolladores no pueden actualizarse de forma segura.

MIGRATION_GUIDES = {
    '1.x_to_2.0': {
        'summary': 'Response structure changed: temperature is now nested under data{}',
        'breaking_changes': [
            {
                'description': 'temperature field moved',
                'before': 'result["temperature"]',
                'after': 'result["data"]["temperature"]'
            },
            {
                'description': 'temp_unit parameter removed',
                'before': 'execute({"city": "London", "temp_unit": "celsius"})',
                'after': 'execute({"city": "London", "units": "celsius"})'
            }
        ],
        'migration_steps': [
            '1. Update parameter name: temp_unit -> units',
            '2. Update response access: result["temperature"] -> result["data"]["temperature"]',
            '3. Run your test suite against v2.0.0'
        ]
    }
}

for guide_key, guide in MIGRATION_GUIDES.items():
    print(f'Migration guide {guide_key}:')
    print(f'  {guide["summary"]}')
    print(f'  Steps: {len(guide["migration_steps"])}')

Matriz de compatibilidad

Una matriz de compatibilidad documenta qué versiones de la herramienta son compatibles con qué versiones del framework de agentes. Publíquela y manténgala como parte de la documentación del plugin.

COMPATIBILITY_MATRIX = {
    'weather-tools': {
        '1.x': {'framework_min': '0.8.0', 'framework_max': '0.x.x', 'status': 'EOL'},
        '2.x': {'framework_min': '1.0.0', 'framework_max': '1.x.x', 'status': 'supported'},
        '3.x': {'framework_min': '2.0.0', 'framework_max': None, 'status': 'latest'}
    }
}

def check_compatibility(
    plugin_name: str,
    tool_version: str,
    framework_version: str
) -> dict:
    matrix = COMPATIBILITY_MATRIX.get(plugin_name, {})
    major = tool_version.split('.')[0] + '.x'
    row = matrix.get(major)
    if not row:
        return {'compatible': False, 'reason': 'Version not in matrix'}
    fw = Version(framework_version)
    min_fw = Version(row['framework_min'])
    compatible = fw >= min_fw
    return {'compatible': compatible, 'status': row['status'],
            'min_framework': row['framework_min']}

result = check_compatibility('weather-tools', '2.3.0', '1.2.0')
print(result)

Registro de versiones

El registro de herramientas debe almacenar la información de versión de cada herramienta cargada y advertir cuando dos plugins proporcionen el mismo nombre de herramienta con versiones diferentes. Dé prioridad a las versiones más recientes, salvo que una restricción especifique lo contrario.

class VersionedToolRegistry(ToolRegistry):
    def register_tool(self, name, schema, execute_fn, plugin_name):
        if name in self._tools:
            existing_v = Version(self._tools[name]['schema'].get('version', '0.0.0'))
            new_v = Version(schema.get('version', '0.0.0'))
            if new_v > existing_v:
                print(f'Upgrading tool {name}: {existing_v} -> {new_v}')
            else:
                print(f'Keeping tool {name} v{existing_v} '
                      f'(skipping older v{new_v} from {plugin_name})')
                return
        super().register_tool(name, schema, execute_fn, plugin_name)

    def get_version(self, tool_name: str) -> str:
        tool = self._tools.get(tool_name)
        if not tool:
            return None
        return tool['schema'].get('version', 'unknown')

    def check_requirement(self, tool_name: str, constraint: str) -> bool:
        v = self.get_version(tool_name)
        if not v:
            return False
        return satisfies_constraint(v, constraint)

Gestión adecuada de las discrepancias de versiones

Cuando se cargue un agente y una herramienta necesaria no cumpla la restricción de versión, no la ignore silenciosamente. Algunas opciones son: fallar de inmediato (la opción más segura), ejecutarse en modo degradado (sin la herramienta) o mostrar una advertencia y continuar.

class VersionCheckResult:
    def __init__(self):
        self.satisfied = []
        self.unsatisfied = []
        self.missing = []

def check_all_requirements(
    requirements: dict,
    registry
) -> VersionCheckResult:
    result = VersionCheckResult()
    for tool_name, constraint in requirements.items():
        installed_v = registry.get_version(tool_name)
        if installed_v is None:
            result.missing.append(tool_name)
        elif not satisfies_constraint(installed_v, constraint):
            result.unsatisfied.append({
                'tool': tool_name,
                'required': constraint,
                'installed': installed_v
            })
        else:
            result.satisfied.append(tool_name)
    return result

def start_agent_with_version_check(requirements, registry):
    check = check_all_requirements(requirements, registry)
    if check.missing:
        raise RuntimeError(f'Missing tools: {check.missing}')
    if check.unsatisfied:
        for item in check.unsatisfied:
            print(f'VERSION MISMATCH: {item["tool"]} '
                  f'requires {item["required"]}, got {item["installed"]}')
        raise RuntimeError('Tool version requirements not satisfied')
    print('All tool requirements satisfied')

Automatización del registro de cambios

Genere automáticamente un registro de cambios a partir de sus mensajes de commit de git mediante el formato Conventional Commits. Así, sus guías de migración y notas de versión estarán siempre actualizadas.

# Conventional commit format:
# feat!: (major) remove temp_unit parameter
# feat: (minor) add 'humidity_pct' to response
# fix: (patch) handle API timeout correctly
# docs: update README

import subprocess

def generate_changelog_from_git(
    from_tag: str = 'v1.2.0',
    to_tag: str = 'HEAD'
) -> dict:
    try:
        log = subprocess.check_output(
            ['git', 'log', f'{from_tag}..{to_tag}',
             '--oneline', '--pretty=format:%s'],
            text=True
        ).strip().split('\n')
    except subprocess.CalledProcessError:
        return {'error': 'git log failed'}

    changelog = {'breaking': [], 'features': [], 'fixes': [], 'docs': []}
    for msg in log:
        if msg.startswith('feat!'):
            changelog['breaking'].append(msg[5:].strip())
        elif msg.startswith('feat:'):
            changelog['features'].append(msg[5:].strip())
        elif msg.startswith('fix:'):
            changelog['fixes'].append(msg[4:].strip())
        elif msg.startswith('docs:'):
            changelog['docs'].append(msg[5:].strip())
    return changelog

if __name__ == '__main__':
    subprocess.check_output = lambda *a, **k: (
        "feat!: remove temp_unit parameter\n"
        "feat: add humidity_pct to response\n"
        "fix: handle API timeout correctly\n"
        "docs: update README"
    )
    changelog = generate_changelog_from_git()
    print('Changelog:')
    for section, items in changelog.items():
        print(f'  {section}: {items}')

Archivos de bloqueo para garantizar la reproducibilidad

Al igual que package-lock.json en npm, mantenga un archivo de bloqueo de la herramienta que registre la versión exacta de cada plugin instalado. Cuando se inicie el agente, verifique que las versiones instaladas coincidan con el archivo de bloqueo para garantizar despliegues reproducibles y predecibles en distintos entornos.

import json
import os

LOCK_FILE = '.tool-lock.json'

def generate_lock_file(registry) -> dict:
    lock = {
        'generated_at': __import__('datetime').datetime.utcnow().isoformat(),
        'tools': {}
    }
    for tool_name, tool_info in registry._tools.items():
        lock['tools'][tool_name] = {
            'version': tool_info['schema'].get('version', 'unknown'),
            'plugin': tool_info['plugin']
        }
    with open(LOCK_FILE, 'w') as f:
        json.dump(lock, f, indent=2)
    print(f'Lock file written: {len(lock["tools"])} tools')
    return lock

def verify_lock_file(registry) -> bool:
    if not os.path.exists(LOCK_FILE):
        print('No lock file found — run generate_lock_file() first')
        return False
    with open(LOCK_FILE) as f:
        lock = json.load(f)
    for tool_name, locked_info in lock['tools'].items():
        installed_v = registry.get_version(tool_name)
        if installed_v != locked_info['version']:
            print(f'VERSION MISMATCH: {tool_name} locked={locked_info["version"]} installed={installed_v}')
            return False
    print('Lock file verified: all versions match')
    return True

if __name__ == '__main__':
    import tempfile
    os.chdir(tempfile.gettempdir())

    class MockRegistry:
        def __init__(self):
            self._tools = {'get_weather': {'schema': {'version': '1.2.0'}, 'plugin': 'weather-tools'}}
        def get_version(self, name):
            return self._tools[name]['schema']['version']

    registry = MockRegistry()
    generate_lock_file(registry)
    verify_lock_file(registry)

Comprobación de conocimientos

Ha añadido un parámetro opcional format a una herramienta existente. Los llamadores actuales no lo utilizan. ¿Qué componente de la versión debe incrementar?

Resumen: versionado y compatibilidad de herramientas

Estas son las conclusiones principales de la lección:

  • Versionado semántico: MAJOR=ruptura de compatibilidad, MINOR=adición compatible, PATCH=corrección
  • Obsolescencia: avise en MINOR, elimine en MAJOR e incluya la obsolescencia en la respuesta
  • Guías de migración: ejemplos de código anterior y posterior para cada incremento MAJOR
  • Matriz de compatibilidad: qué versiones de la herramienta funcionan con qué versiones del framework
  • Fijación de versiones: la configuración del agente especifica restricciones como >=1.2.0,<2.0.0
  • Fallo rápido: rechace la carga de herramientas que no cumplan las restricciones

A continuación: creación de un marketplace de plugins para agentes con funciones de publicación, descubrimiento e instalación.

Preguntas frecuentes

¿La lección «Versionado y compatibilidad de herramientas» es gratis?

Sí — el texto completo de «Versionado y compatibilidad de herramientas» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Agents, actualiza a CoddyKit PRO. El curso de AI Agents incluye 4 lecciones en total.

¿Qué aprenderé en «Versionado y compatibilidad de herramientas»?

Versionado semántico de herramientas, compatibilidad con versiones anteriores y patrones de obsolescencia. Practicas AI Agents con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar AI Agents?

No se requiere experiencia previa. AI Agents en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «Versionado y compatibilidad de herramientas»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de AI Agents?

Sí. Cada lección de AI Agents incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Diseño de herramientas de agentes reutilizables
  2. Descubrimiento y registro de plugins
  3. Versionado y compatibilidad de herramientas
  4. Creación de un marketplace de plugins para agentes
← Volver a AI Agents