{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "465cfbef-5bba-4b3b-b02d-fe2eba39db17",
   "metadata": {},
   "source": [
    "# Evaluating Structured Output: JSON Evaluators\n",
    "\n",
    "Evaluating [extraction](https://python.langchain.com/docs/use_cases/extraction) and function calling applications often comes down to validation that the LLM's string output can be parsed correctly and how it compares to a reference object. The following JSON validators provide provide functionality to check your model's output in a consistent way.\n",
    "\n",
    "## JsonValidityEvaluator\n",
    "\n",
    "The `JsonValidityEvaluator` is designed to check the validity of a JSON string prediction.\n",
    "\n",
    "### Overview:\n",
    "- **Requires Input?**: No\n",
    "- **Requires Reference?**: No"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "02e5f7dd-82fe-48f9-a251-b2052e17e61c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'score': 1}\n"
     ]
    }
   ],
   "source": [
    "from langchain.evaluation import JsonValidityEvaluator\n",
    "\n",
    "evaluator = JsonValidityEvaluator()\n",
    "# Equivalently\n",
    "# evaluator = load_evaluator(\"json_validity\")\n",
    "prediction = '{\"name\": \"John\", \"age\": 30, \"city\": \"New York\"}'\n",
    "\n",
    "result = evaluator.evaluate_strings(prediction=prediction)\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "9a9607c6-edab-4c26-86c4-22b226e18aa9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'score': 0, 'reasoning': 'Expecting property name enclosed in double quotes: line 1 column 48 (char 47)'}\n"
     ]
    }
   ],
   "source": [
    "prediction = '{\"name\": \"John\", \"age\": 30, \"city\": \"New York\",}'\n",
    "result = evaluator.evaluate_strings(prediction=prediction)\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ac18a83-30d8-4c11-abf2-7a36e4cb829f",
   "metadata": {},
   "source": [
    "## JsonEqualityEvaluator\n",
    "\n",
    "The `JsonEqualityEvaluator` assesses whether a JSON prediction matches a given reference after both are parsed.\n",
    "\n",
    "### Overview:\n",
    "- **Requires Input?**: No\n",
    "- **Requires Reference?**: Yes\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ab97111e-cba9-4273-825f-d5d4278a953c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'score': True}\n"
     ]
    }
   ],
   "source": [
    "from langchain.evaluation import JsonEqualityEvaluator\n",
    "\n",
    "evaluator = JsonEqualityEvaluator()\n",
    "# Equivalently\n",
    "# evaluator = load_evaluator(\"json_equality\")\n",
    "result = evaluator.evaluate_strings(prediction='{\"a\": 1}', reference='{\"a\": 1}')\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "655ba486-09b6-47ce-947d-b2bd8b6f6364",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'score': False}\n"
     ]
    }
   ],
   "source": [
    "result = evaluator.evaluate_strings(prediction='{\"a\": 1}', reference='{\"a\": 2}')\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ac7e541-b7fe-46b6-bc3a-e94fe316227e",
   "metadata": {},
   "source": [
    "The evaluator also by default lets you provide a dictionary directly"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "36e70ba3-4e62-483c-893a-5f328b7f303d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'score': False}\n"
     ]
    }
   ],
   "source": [
    "result = evaluator.evaluate_strings(prediction={\"a\": 1}, reference={\"a\": 2})\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "921d33f0-b3c2-4e9e-820c-9ec30bc5bb20",
   "metadata": {},
   "source": [
    "## JsonEditDistanceEvaluator\n",
    "\n",
    "The `JsonEditDistanceEvaluator` computes a normalized Damerau-Levenshtein distance between two \"canonicalized\" JSON strings.\n",
    "\n",
    "### Overview:\n",
    "- **Requires Input?**: No\n",
    "- **Requires Reference?**: Yes\n",
    "- **Distance Function**: Damerau-Levenshtein (by default)\n",
    "\n",
    "_Note: Ensure that `rapidfuzz` is installed or provide an alternative `string_distance` function to avoid an ImportError._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "da9ec3a3-675f-4420-8ec7-cde48d8c2918",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'score': 0.07692307692307693}\n"
     ]
    }
   ],
   "source": [
    "from langchain.evaluation import JsonEditDistanceEvaluator\n",
    "\n",
    "evaluator = JsonEditDistanceEvaluator()\n",
    "# Equivalently\n",
    "# evaluator = load_evaluator(\"json_edit_distance\")\n",
    "\n",
    "result = evaluator.evaluate_strings(\n",
    "    prediction='{\"a\": 1, \"b\": 2}', reference='{\"a\": 1, \"b\": 3}'\n",
    ")\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "537ed58c-6a9c-402f-8f7f-07b1119a9ae0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'score': 0.0}\n"
     ]
    }
   ],
   "source": [
    "# The values are canonicalized prior to comparison\n",
    "result = evaluator.evaluate_strings(\n",
    "    prediction=\"\"\"\n",
    "    {\n",
    "        \"b\": 3,\n",
    "        \"a\":   1\n",
    "    }\"\"\",\n",
    "    reference='{\"a\": 1, \"b\": 3}',\n",
    ")\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "7a8f3ec5-1cde-4b0e-80cd-ac0ac290d375",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'score': 0.18181818181818182}\n"
     ]
    }
   ],
   "source": [
    "# Lists maintain their order, however\n",
    "result = evaluator.evaluate_strings(\n",
    "    prediction='{\"a\": [1, 2]}', reference='{\"a\": [2, 1]}'\n",
    ")\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "52abec79-58ed-4ab6-9fb1-7deb1f5146cc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'score': 0.14285714285714285}\n"
     ]
    }
   ],
   "source": [
    "# You can also pass in objects directly\n",
    "result = evaluator.evaluate_strings(prediction={\"a\": 1}, reference={\"a\": 2})\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b15d18e-9b97-434f-905c-70acd4c35aea",
   "metadata": {},
   "source": [
    "## JsonSchemaEvaluator\n",
    "\n",
    "The `JsonSchemaEvaluator` validates a JSON prediction against a provided JSON schema. If the prediction conforms to the schema, it returns a score of True (indicating no errors). Otherwise, it returns a score of 0 (indicating an error).\n",
    "\n",
    "### Overview:\n",
    "- **Requires Input?**: Yes\n",
    "- **Requires Reference?**: Yes (A JSON schema)\n",
    "- **Score**: True (No errors) or False (Error occurred)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "85afcf33-d2f4-406e-9d8f-15dc0a4772f2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'score': True}\n"
     ]
    }
   ],
   "source": [
    "from langchain.evaluation import JsonSchemaEvaluator\n",
    "\n",
    "evaluator = JsonSchemaEvaluator()\n",
    "# Equivalently\n",
    "# evaluator = load_evaluator(\"json_schema_validation\")\n",
    "\n",
    "result = evaluator.evaluate_strings(\n",
    "    prediction='{\"name\": \"John\", \"age\": 30}',\n",
    "    reference={\n",
    "        \"type\": \"object\",\n",
    "        \"properties\": {\"name\": {\"type\": \"string\"}, \"age\": {\"type\": \"integer\"}},\n",
    "    },\n",
    ")\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "bb5b89f6-0c87-4335-9091-55fd67a0565f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'score': True}\n"
     ]
    }
   ],
   "source": [
    "result = evaluator.evaluate_strings(\n",
    "    prediction='{\"name\": \"John\", \"age\": 30}',\n",
    "    reference='{\"type\": \"object\", \"properties\": {\"name\": {\"type\": \"string\"}, \"age\": {\"type\": \"integer\"}}}',\n",
    ")\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "ff914d24-36bc-482a-a9ba-259cd0dd2a52",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'score': False, 'reasoning': \"<ValidationError: '30 is less than the minimum of 66'>\"}\n"
     ]
    }
   ],
   "source": [
    "result = evaluator.evaluate_strings(\n",
    "    prediction='{\"name\": \"John\", \"age\": 30}',\n",
    "    reference='{\"type\": \"object\", \"properties\": {\"name\": {\"type\": \"string\"},'\n",
    "    '\"age\": {\"type\": \"integer\", \"minimum\": 66}}}',\n",
    ")\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b073f12d-4603-481c-8081-fab1af6bfcfe",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
