# Extract Relations from Text (LLM-based)

Diseño original para fuzzygraph.

## Funcion a implementar

### extract_relations_llm

- **Dominio:** datascience
- **Lang:** Python
- **Purity:** impure (llama LLM)
- **Signature:**
  ```python
  def extract_relations_llm(
      text: str,
      entities: list[EntityCandidate],
      relation_types: list[str],
      llm_chat_json: Callable[[list[dict]], dict],
      language_instruction: str = "Respond in English.",
  ) -> list[RelationCandidate]:
  ```
- **Parametros:**
  - `text`: chunk de texto (mismo que se uso para extraer entidades)
  - `entities`: entidades ya extraidas de este chunk (para que el LLM sepa entre que entidades buscar relaciones)
  - `relation_types`: tipos de relacion permitidos, ej: `["funds", "employs", "communicates_with", "owns", "operates", "controls", "affiliated_with", "located_at", "resolves_to", "hosts", "exploits", "attributed_to", "related_to"]`
  - `llm_chat_json`: funcion inyectada
- **Retorno:** `list[RelationCandidate]` (ver spec fg_09)
- **Algoritmo:**
  1. Si `len(entities) < 2`: retornar [] (necesitas al menos 2 entidades para una relacion)
  2. Construir system prompt:
     ```
     You are a relation extraction expert. Given text and a list of
     entities already extracted, identify relationships between them.
     
     Entities found in this text:
     - "John Smith" (Person)
     - "Acme Corp" (Organization)
     - "192.168.1.1" (IP Address)
     
     Allowed relation types: funds, employs, communicates_with, ...
     
     Output JSON: {"relations": [
       {"from_name": "Acme Corp", "to_name": "John Smith",
        "relation_type": "employs", "description": "...", "confidence": 0.8}
     ]}
     
     Rules:
     - Only extract relations explicitly stated or strongly implied
     - from_name and to_name must match entity names exactly
     - relation_type must be from the allowed list
     - Confidence: 1.0 = explicitly stated, 0.7 = strongly implied
     ```
  3. User message: el texto
  4. Parsear respuesta, validar from/to contra nombres de entities
  5. Retornar lista de RelationCandidate
- **Error handling:**
  - from_name o to_name no matchean ninguna entidad: descartar relacion
  - relation_type no en lista: usar "related_to" como fallback
- **Deps:** ninguna especifica
- **Tests:**
  - Texto con 2 entidades relacionadas
  - Texto con entidades pero sin relacion (retorna [])
  - Menos de 2 entidades (retorna [])
  - LLM inventa entidad que no existe (se descarta)
