diff --git a/nbs/backends/ragas_api_client.ipynb b/nbs/backends/ragas_api_client.ipynb
index 33e9f9f..0834e13 100644
--- a/nbs/backends/ragas_api_client.ipynb
+++ b/nbs/backends/ragas_api_client.ipynb
@@ -704,25 +704,7 @@
"outputs": [],
"source": [
"#| export\n",
- "from enum import Enum"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "class ColumnType(str, Enum):\n",
- " NUMBER = \"number\"\n",
- " TEXT = \"text\"\n",
- " LONG_TEXT = \"longText\"\n",
- " SELECT = \"select\"\n",
- " DATE = \"date\"\n",
- " MULTI_SELECT = \"multiSelect\"\n",
- " CHECKBOX = \"checkbox\"\n",
- " CUSTOM = \"custom\""
+ "from ragas_experimental.typing import ColumnType"
]
},
{
diff --git a/nbs/dataset.ipynb b/nbs/dataset.ipynb
index 9ad3005..7eadb5d 100644
--- a/nbs/dataset.ipynb
+++ b/nbs/dataset.ipynb
@@ -149,7 +149,8 @@
"source": [
"# | hide\n",
"import ragas_experimental.typing as rt\n",
- "from ragas_experimental.backends.factory import RagasApiClientFactory"
+ "from ragas_experimental.backends.factory import RagasApiClientFactory\n",
+ "from ragas_experimental.metric.result import MetricResult"
]
},
{
@@ -162,27 +163,17 @@
"class TestModel(BaseModel):\n",
" id: int\n",
" name: str\n",
- " description: str"
+ " description: str\n",
+ " result: MetricResult"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "{'id': 'id', 'name': 'name', 'description': 'description'}"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "TestModel.__column_mapping__"
+ "TestModel.__column_mapping__ = {}"
]
},
{
@@ -193,7 +184,7 @@
{
"data": {
"text/plain": [
- "TestModel(id=0, name='test', description='test description')"
+ "TestModel(id=0, name='test', description='test description', result=0.5)"
]
},
"execution_count": null,
@@ -202,7 +193,7 @@
}
],
"source": [
- "test_model = TestModel(id=0, name=\"test\", description=\"test description\")\n",
+ "test_model = TestModel(id=0, name=\"test\", description=\"test description\", result=MetricResult(result=0.5, reason=\"test reason\"))\n",
"test_model"
]
},
@@ -214,7 +205,11 @@
{
"data": {
"text/plain": [
- "{'id': 'id', 'name': 'name', 'description': 'description'}"
+ "{'id': 'id',\n",
+ " 'name': 'name',\n",
+ " 'description': 'description',\n",
+ " 'result': 'result',\n",
+ " 'result_reason': 'result_reason'}"
]
},
"execution_count": null,
@@ -232,7 +227,8 @@
"metadata": {},
"outputs": [],
"source": [
- "import os"
+ "import os\n",
+ "from ragas_experimental import Project"
]
},
{
@@ -265,13 +261,35 @@
{
"data": {
"text/plain": [
- "{'id': '0fee5330-9f6e-44a9-a85c-e3b947b697de',\n",
- " 'name': 'TestModel',\n",
+ "Dataset(name=TestModel_with_long_text, model=TestModel, len=0)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "p = Project(project_id=\"1ef0843b-231f-4a2c-b64d-d39bcee9d830\", ragas_app_client=ragas_api_client)\n",
+ "test_dataset = p.create_dataset(name=\"TestModel_with_long_text\", model=TestModel)\n",
+ "test_dataset"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'id': 'a7bd8b1f-dc29-4ae0-b1e5-24f0dfb76f7a',\n",
+ " 'name': 'TestModel_with_long_text',\n",
" 'description': None,\n",
- " 'created_at': '2025-04-10T22:41:36.582714+00:00',\n",
- " 'updated_at': '2025-04-10T22:41:36.582714+00:00',\n",
+ " 'created_at': '2025-04-16T00:02:58.149112+00:00',\n",
+ " 'updated_at': '2025-04-16T00:02:58.149112+00:00',\n",
" 'version_counter': 0,\n",
- " 'project_id': 'bbe45632-3268-43a6-9694-b020b3f5226f'}"
+ " 'project_id': '1ef0843b-231f-4a2c-b64d-d39bcee9d830'}"
]
},
"execution_count": null,
@@ -281,13 +299,38 @@
],
"source": [
"# https://dev.app.ragas.io/dashboard/projects/0a7c4ecb-b313-4bb0-81c0-852c9634ce03/datasets/a4f0d169-ebce-4a2b-b758-0ff49c0c4312\n",
- "TEST_PROJECT_ID = \"bbe45632-3268-43a6-9694-b020b3f5226f\"\n",
- "TEST_DATASET_ID = \"0fee5330-9f6e-44a9-a85c-e3b947b697de\"\n",
+ "TEST_PROJECT_ID = p.project_id\n",
+ "TEST_DATASET_ID = test_dataset.dataset_id\n",
"test_project = await ragas_api_client.get_project(project_id=TEST_PROJECT_ID)\n",
"test_dataset = await ragas_api_client.get_dataset(project_id=TEST_PROJECT_ID, dataset_id=TEST_DATASET_ID)\n",
"test_dataset"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'items': [],\n",
+ " 'pagination': {'offset': 0,\n",
+ " 'limit': 50,\n",
+ " 'total': 0,\n",
+ " 'order_by': 'created_at',\n",
+ " 'sort_dir': 'asc'}}"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "await ragas_api_client.list_dataset_rows(project_id=TEST_PROJECT_ID, dataset_id=TEST_DATASET_ID)"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -307,7 +350,11 @@
{
"data": {
"text/plain": [
- "{'id': 'fSh3ESKGNZLf', 'description': 'ftKwfDqnbinL', 'name': 'fVnsYEnvxARh'}"
+ "{'id': 'eCYjJtBbLSDL',\n",
+ " 'name': '1sfEb8iJ1TUg',\n",
+ " 'description': 'gYsqmPXHbOyy',\n",
+ " 'result': 'cAZanmY0CwWT',\n",
+ " 'result_reason': 'e7IgZXICkmhA'}"
]
},
"execution_count": null,
@@ -319,6 +366,16 @@
"dataset.model.__column_mapping__"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "import ragas_experimental.typing as rt"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -335,12 +392,12 @@
" column_id_map = self.model.__column_mapping__\n",
"\n",
" # create the rows\n",
- " row_dict = entry.model_dump()\n",
+ " row_dict_converted = rt.ModelConverter.instance_to_row(entry)\n",
" row_id = create_nano_id()\n",
" row_data = {}\n",
- " for key, value in row_dict.items():\n",
- " if key in column_id_map:\n",
- " row_data[column_id_map[key]] = value\n",
+ " for column in row_dict_converted[\"data\"]:\n",
+ " if column[\"column_id\"] in column_id_map:\n",
+ " row_data[column_id_map[column[\"column_id\"]]] = column[\"data\"]\n",
"\n",
" sync_func = async_to_sync(self._ragas_api_client.create_dataset_row)\n",
" response = sync_func(\n",
@@ -363,7 +420,7 @@
{
"data": {
"text/plain": [
- "3"
+ "1"
]
},
"execution_count": null,
@@ -418,7 +475,7 @@
{
"data": {
"text/plain": [
- "1"
+ "0"
]
},
"execution_count": null,
@@ -441,6 +498,29 @@
"test_eq(len(dataset), 0)"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "12"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# now add some more entries\n",
+ "for i in range(10):\n",
+ " dataset.append(test_model)\n",
+ "len(dataset)"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -534,8 +614,66 @@
{
"data": {
"text/plain": [
- "[{'id': 0, 'name': 'test', 'description': 'test description'},\n",
- " {'id': 0, 'name': 'test', 'description': 'test description'}]"
+ "[{'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0}]"
]
},
"execution_count": null,
@@ -597,6 +735,7 @@
"
id | \n",
" name | \n",
" description | \n",
+ " result | \n",
" \n",
" \n",
" \n",
@@ -605,21 +744,103 @@
" 0 | \n",
" test | \n",
" test description | \n",
+ " 0.5 | \n",
" \n",
" \n",
" | 1 | \n",
" 0 | \n",
" test | \n",
" test description | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 0 | \n",
+ " test | \n",
+ " test description | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 0 | \n",
+ " test | \n",
+ " test description | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 0 | \n",
+ " test | \n",
+ " test description | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " 0 | \n",
+ " test | \n",
+ " test description | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " | 6 | \n",
+ " 0 | \n",
+ " test | \n",
+ " test description | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " | 7 | \n",
+ " 0 | \n",
+ " test | \n",
+ " test description | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " | 8 | \n",
+ " 0 | \n",
+ " test | \n",
+ " test description | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " | 9 | \n",
+ " 0 | \n",
+ " test | \n",
+ " test description | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " | 10 | \n",
+ " 0 | \n",
+ " test | \n",
+ " test description | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " | 11 | \n",
+ " 0 | \n",
+ " test | \n",
+ " test description | \n",
+ " 0.5 | \n",
"
\n",
" \n",
"\n",
""
],
"text/plain": [
- " id name description\n",
- "0 0 test test description\n",
- "1 0 test test description"
+ " id name description result\n",
+ "0 0 test test description 0.5\n",
+ "1 0 test test description 0.5\n",
+ "2 0 test test description 0.5\n",
+ "3 0 test test description 0.5\n",
+ "4 0 test test description 0.5\n",
+ "5 0 test test description 0.5\n",
+ "6 0 test test description 0.5\n",
+ "7 0 test test description 0.5\n",
+ "8 0 test test description 0.5\n",
+ "9 0 test test description 0.5\n",
+ "10 0 test test description 0.5\n",
+ "11 0 test test description 0.5"
]
},
"execution_count": null,
@@ -661,12 +882,12 @@
" \n",
" # Get column mapping and prepare data\n",
" column_id_map = self.model.__column_mapping__\n",
- " row_dict = item.model_dump()\n",
+ " row_dict = rt.ModelConverter.instance_to_row(item)[\"data\"]\n",
" row_data = {}\n",
" \n",
- " for key, value in row_dict.items():\n",
- " if key in column_id_map:\n",
- " row_data[column_id_map[key]] = value\n",
+ " for column in row_dict:\n",
+ " if column[\"column_id\"] in column_id_map:\n",
+ " row_data[column_id_map[column[\"column_id\"]]] = column[\"data\"]\n",
" \n",
" # Update in backend\n",
" sync_func = async_to_sync(self._ragas_api_client.update_dataset_row)\n",
@@ -694,7 +915,7 @@
{
"data": {
"text/plain": [
- "TestModel(id=0, name='test', description='test description')"
+ "TestModel(id=0, name='updated name', description='test description', result=0.5)"
]
},
"execution_count": null,
@@ -737,8 +958,66 @@
{
"data": {
"text/plain": [
- "[{'id': 0, 'name': 'updated name', 'description': 'test description'},\n",
- " {'id': 0, 'name': 'test', 'description': 'test description'}]"
+ "[{'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'test',\n",
+ " 'id': 0},\n",
+ " {'description': 'test description',\n",
+ " 'result': 0.5,\n",
+ " 'result_reason': 'test reason',\n",
+ " 'name': 'updated name',\n",
+ " 'id': 0}]"
]
},
"execution_count": null,
@@ -808,7 +1087,7 @@
{
"data": {
"text/plain": [
- "'eWlNKu6F4jIX'"
+ "'eVpgxsmPGwa8'"
]
},
"execution_count": null,
@@ -828,7 +1107,7 @@
{
"data": {
"text/plain": [
- "TestModel(id=0, name='updated name', description='test description')"
+ "TestModel(id=0, name='updated name', description='test description', result=0.5)"
]
},
"execution_count": null,
diff --git a/nbs/metric/result.ipynb b/nbs/metric/result.ipynb
index ecbab85..68ccd6b 100644
--- a/nbs/metric/result.ipynb
+++ b/nbs/metric/result.ipynb
@@ -291,21 +291,53 @@
" return value\n",
" return MetricResult(result=value)\n",
"\n",
- "# Add Pydantic compatibility methods\n",
+ "@patch\n",
+ "def __json__(self: MetricResult):\n",
+ " \"\"\"Return data for JSON serialization.\n",
+ " \n",
+ " This method is used by json.dumps and other JSON serializers \n",
+ " to convert MetricResult to a JSON-compatible format.\n",
+ " \"\"\"\n",
+ " return {\n",
+ " \"result\": self._result,\n",
+ " \"reason\": self.reason,\n",
+ " }\n",
+ "\n",
"@patch(cls_method=True)\n",
"def __get_pydantic_core_schema__(\n",
" cls: MetricResult, \n",
" _source_type: t.Any, \n",
" _handler: GetCoreSchemaHandler\n",
") -> core_schema.CoreSchema:\n",
- " \"\"\"Generate a Pydantic core schema for MetricResult.\"\"\"\n",
- " return core_schema.with_info_plain_validator_function(cls.validate)\n",
- "\n",
- "\n",
- "@patch\n",
- "def model_dump(self: MetricResult):\n",
- " \"\"\"Support Pydantic's model_dump method.\"\"\"\n",
- " return self.to_dict()"
+ " \"\"\"Generate a Pydantic core schema for MetricResult.\n",
+ " \n",
+ " This custom schema handles different serialization behaviors:\n",
+ " - For model_dump(): Returns the original MetricResult instance\n",
+ " - For model_dump_json(): Converts to a JSON-compatible dict using __json__\n",
+ " \"\"\"\n",
+ " def serializer_function(instance, info):\n",
+ " \"\"\"Handle different serialization modes for MetricResult.\"\"\"\n",
+ " # For JSON serialization (model_dump_json), use __json__ method\n",
+ " if getattr(info, 'mode', None) == 'json':\n",
+ " return instance.__json__()\n",
+ " # For Python serialization (model_dump), return the instance itself\n",
+ " return instance\n",
+ " \n",
+ " return core_schema.union_schema([\n",
+ " # First schema: handles validation of MetricResult instances\n",
+ " core_schema.is_instance_schema(MetricResult),\n",
+ " \n",
+ " # Second schema: handles validation of other values and conversion to MetricResult\n",
+ " core_schema.chain_schema([\n",
+ " core_schema.any_schema(),\n",
+ " core_schema.no_info_plain_validator_function(\n",
+ " lambda value: MetricResult(result=value) if not isinstance(value, MetricResult) else value\n",
+ " ),\n",
+ " ]),\n",
+ " ], serialization=core_schema.plain_serializer_function_ser_schema(\n",
+ " serializer_function,\n",
+ " info_arg=True # Explicitly specify that we're using the info argument\n",
+ " ))"
]
},
{
@@ -328,9 +360,21 @@
"execution_count": null,
"id": "6ac6b955",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "TestModel(response='test', grade=1, faithfulness=1)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "m = TestModel(response=\"test\", grade=MetricResult(result=1, reason=\"test\"), faithfulness=MetricResult(result=1, reason=\"test\"))"
+ "m = TestModel(response=\"test\", grade=MetricResult(result=1, reason=\"test\"), faithfulness=MetricResult(result=1, reason=\"test\"))\n",
+ "m"
]
},
{
@@ -342,7 +386,7 @@
{
"data": {
"text/plain": [
- "'test'"
+ "{'response': 'test', 'grade': 1, 'faithfulness': 1}"
]
},
"execution_count": null,
@@ -351,16 +395,29 @@
}
],
"source": [
- "m.grade.reason"
+ "m.model_dump()"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "9d32b10f",
+ "id": "0bc2a1ec",
"metadata": {},
- "outputs": [],
- "source": []
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'{\"response\":\"test\",\"grade\":{\"result\":1,\"reason\":\"test\"},\"faithfulness\":{\"result\":1,\"reason\":\"test\"}}'"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m.model_dump_json()"
+ ]
}
],
"metadata": {
diff --git a/nbs/model/pydantic_mode.ipynb b/nbs/model/pydantic_mode.ipynb
index 5e644ba..59be463 100644
--- a/nbs/model/pydantic_mode.ipynb
+++ b/nbs/model/pydantic_mode.ipynb
@@ -70,6 +70,24 @@
" column_id = field_name\n",
" \n",
" cls.__column_mapping__[field_name] = column_id\n",
+ "\n",
+ " # check if the field is a MetricResult\n",
+ " if cls._is_metric_result_field(field_info.annotation):\n",
+ " # add additional mapping for the metric result\n",
+ " reason_field_name = f\"{field_name}_reason\"\n",
+ " reason_column_id = f\"{column_id}_reason\"\n",
+ " cls.__column_mapping__[reason_field_name] = reason_column_id\n",
+ "\n",
+ " @staticmethod\n",
+ " def _is_metric_result_field(annotation):\n",
+ " \"\"\"Check if a field annotation represents a MetricResult.\"\"\"\n",
+ " # Direct import of MetricResult\n",
+ " from ragas_experimental.metric.result import MetricResult\n",
+ " \n",
+ " # Check if annotation is or references MetricResult\n",
+ " return (annotation is MetricResult or \n",
+ " (hasattr(annotation, \"__origin__\") and annotation.__origin__ is MetricResult) or\n",
+ " (hasattr(annotation, \"__class__\") and annotation.__class__ is MetricResult))\n",
" \n",
" @classmethod\n",
" def get_column_id(cls, field_name: str) -> str:\n",
@@ -97,7 +115,8 @@
"metadata": {},
"outputs": [],
"source": [
- "import ragas_experimental.typing as rt"
+ "import ragas_experimental.typing as rt\n",
+ "from ragas_experimental.metric.result import MetricResult"
]
},
{
@@ -110,7 +129,17 @@
"class TestDataRow(ExtendedPydanticBaseModel):\n",
" id: t.Optional[int] = None\n",
" query: t.Annotated[str, rt.Text(id=\"search_query\")]\n",
- " persona: t.List[t.Literal[\"opt1\", \"opt2\", \"opt3\"]]"
+ " persona: t.List[t.Literal[\"opt1\", \"opt2\", \"opt3\"]]\n",
+ " result: MetricResult"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "TestDataRow.__column_mapping__ = {}"
]
},
{
@@ -121,7 +150,7 @@
{
"data": {
"text/plain": [
- "{}"
+ "TestDataRow(id=1, query='this is a test', persona=['opt1'], result=0.5)"
]
},
"execution_count": null,
@@ -130,16 +159,8 @@
}
],
"source": [
- "TestDataRow.__column_mapping__"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "t = TestDataRow(id=1, query=\"this is a test\", persona=[\"opt1\"])"
+ "test_data_row = TestDataRow(id=1, query=\"this is a test\", persona=[\"opt1\"], result=MetricResult(result=0.5, reason=\"test reason\"))\n",
+ "test_data_row"
]
},
{
@@ -150,7 +171,11 @@
{
"data": {
"text/plain": [
- "{'id': 'id', 'query': 'search_query', 'persona': 'persona'}"
+ "{'id': 'id',\n",
+ " 'query': 'search_query',\n",
+ " 'persona': 'persona',\n",
+ " 'result': 'result',\n",
+ " 'result_reason': 'result_reason'}"
]
},
"execution_count": null,
@@ -159,7 +184,7 @@
}
],
"source": [
- "t.__column_mapping__"
+ "test_data_row.__column_mapping__"
]
},
{
diff --git a/nbs/typing.ipynb b/nbs/typing.ipynb
index cbab5ff..3789ad8 100644
--- a/nbs/typing.ipynb
+++ b/nbs/typing.ipynb
@@ -53,7 +53,7 @@
"class ColumnType(str, Enum):\n",
" \"\"\"Column types supported by the Ragas API.\"\"\"\n",
" NUMBER = \"number\"\n",
- " TEXT = \"text\"\n",
+ " TEXT = \"longText\"\n",
" SELECT = \"select\"\n",
" MULTI_SELECT = \"multiSelect\"\n",
" CHECKBOX = \"checkbox\"\n",
@@ -404,141 +404,6 @@
" return [cls.instance_to_row(instance, model_class) for instance in instances]"
]
},
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "# class ModelConverter:\n",
- "# \"\"\"Convert Pydantic models to Ragas API columns and rows.\"\"\"\n",
- " \n",
- "# @staticmethod\n",
- "# def infer_field_type(annotation, field_info=None):\n",
- "# \"\"\"Infer field type from Python type annotation.\"\"\"\n",
- "# # Check for Annotated with our custom metadata\n",
- "# origin = t.get_origin(annotation)\n",
- "# args = t.get_args(annotation)\n",
- " \n",
- "# # If this is an Annotated field with our metadata\n",
- "# if origin is t.Annotated and len(args) > 1:\n",
- "# for arg in args[1:]:\n",
- "# if isinstance(arg, FieldMeta):\n",
- "# return arg\n",
- " \n",
- "# # If no field metadata found, infer from the base type\n",
- "# return ModelConverter.infer_field_type(args[0], field_info)\n",
- " \n",
- "# # Handle Optional, List, etc.\n",
- "# if origin is t.Union:\n",
- "# if type(None) in args:\n",
- "# # This is Optional[T]\n",
- "# non_none_args = [arg for arg in args if arg is not type(None)]\n",
- "# if len(non_none_args) == 1:\n",
- "# # Get the field type of the non-None arg\n",
- "# field_meta = ModelConverter.infer_field_type(non_none_args[0], field_info)\n",
- "# field_meta.required = False\n",
- "# return field_meta\n",
- " \n",
- "# # Handle List and array types\n",
- "# # NOTE: here we are converting lists to strings, except for literal types\n",
- "# if origin is list or origin is t.List:\n",
- "# if len(args) > 0:\n",
- "# # Check if it's a list of literals\n",
- "# if t.get_origin(args[0]) is t.Literal:\n",
- "# literal_options = t.get_args(args[0])\n",
- "# return MultiSelect(options=list(literal_options))\n",
- "# # Otherwise just a regular list\n",
- "# return Text() # Default to Text for lists\n",
- " \n",
- "# # Handle Literal\n",
- "# if origin is t.Literal:\n",
- "# return Select(options=list(args))\n",
- " \n",
- "# # Basic type handling\n",
- "# if annotation is str:\n",
- "# return Text()\n",
- "# elif annotation is int or annotation is float:\n",
- "# return Number()\n",
- "# elif annotation is bool:\n",
- "# return Checkbox()\n",
- "# elif annotation is datetime or annotation is date:\n",
- "# return Date(include_time=annotation is datetime)\n",
- " \n",
- "# # Default to Text for complex or unknown types\n",
- "# return Text()\n",
- " \n",
- "# @classmethod\n",
- "# def model_to_columns(cls, model_class):\n",
- "# \"\"\"Convert a Pydantic model class to Ragas API column definitions.\"\"\"\n",
- "# columns = []\n",
- "# for field_name, field_info in model_class.model_fields.items():\n",
- "# # Get the field's type annotation\n",
- "# annotation = field_info.annotation\n",
- " \n",
- "# # Try to get field metadata\n",
- "# field_meta = cls.infer_field_type(annotation, field_info)\n",
- " \n",
- "# # Create column definition\n",
- "# column = {\n",
- "# \"id\": field_name,\n",
- "# \"name\": field_name,\n",
- "# \"type\": field_meta.type.value,\n",
- "# \"settings\": field_meta.settings.copy()\n",
- "# }\n",
- " \n",
- "# columns.append(column)\n",
- " \n",
- "# return columns\n",
- " \n",
- "# @classmethod\n",
- "# def instance_to_row(cls, instance, model_class=None):\n",
- "# \"\"\"Convert a Pydantic model instance to a Ragas API row.\"\"\"\n",
- "# if model_class is None:\n",
- "# model_class = instance.__class__\n",
- " \n",
- "# row_cells = []\n",
- "# model_data = instance.model_dump()\n",
- " \n",
- "# for field_name, field_info in model_class.model_fields.items():\n",
- "# if field_name in model_data:\n",
- "# value = model_data[field_name]\n",
- "# # Process value based on field type\n",
- "# annotation = field_info.annotation\n",
- "# field_meta = cls.infer_field_type(annotation, field_info)\n",
- " \n",
- "# # Special handling for various types\n",
- "# if field_meta.type == ColumnType.MULTI_SELECT and isinstance(value, list):\n",
- "# # Convert list to string format accepted by API\n",
- "# processed_value = value\n",
- "# elif field_meta.type == ColumnType.DATE and isinstance(value, (datetime, date)):\n",
- "# # Format date as string\n",
- "# processed_value = value.isoformat()\n",
- "# else:\n",
- "# processed_value = value\n",
- " \n",
- "# row_cells.append({\n",
- "# \"column_id\": field_name,\n",
- "# \"data\": processed_value\n",
- "# })\n",
- " \n",
- "# return {\n",
- "# \"data\": row_cells\n",
- "# }\n",
- " \n",
- "# @classmethod\n",
- "# def instances_to_rows(cls, instances, model_class=None):\n",
- "# \"\"\"Convert multiple Pydantic model instances to Ragas API rows.\"\"\"\n",
- "# if not instances:\n",
- "# return []\n",
- " \n",
- "# if model_class is None and instances:\n",
- "# model_class = instances[0].__class__\n",
- " \n",
- "# return [cls.instance_to_row(instance, model_class) for instance in instances]"
- ]
- },
{
"cell_type": "code",
"execution_count": null,
@@ -607,24 +472,18 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "Testing model_to_columns:\n"
- ]
- },
- {
- "ename": "ImportError",
- "evalue": "cannot import name 'Project' from partially initialized module 'ragas_experimental.project.core' (most likely due to a circular import) (/Users/jjmachan/workspace/eglabs/ragas_experimental/ragas_experimental/project/core.py)",
- "output_type": "error",
- "traceback": [
- "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
- "\u001b[31mImportError\u001b[39m Traceback (most recent call last)",
- "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[15]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# Test the model_to_columns method\u001b[39;00m\n\u001b[32m 2\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33mTesting model_to_columns:\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m columns = \u001b[43mModelConverter\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmodel_to_columns\u001b[49m\u001b[43m(\u001b[49m\u001b[43mTestModel\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 4\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m col \u001b[38;5;129;01min\u001b[39;00m columns:\n\u001b[32m 5\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m- \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mcol[\u001b[33m'\u001b[39m\u001b[33mname\u001b[39m\u001b[33m'\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m (\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mcol[\u001b[33m'\u001b[39m\u001b[33mtype\u001b[39m\u001b[33m'\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m): \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mcol[\u001b[33m'\u001b[39m\u001b[33msettings\u001b[39m\u001b[33m'\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n",
- "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[12]\u001b[39m\u001b[32m, line 90\u001b[39m, in \u001b[36mModelConverter.model_to_columns\u001b[39m\u001b[34m(cls, model_class)\u001b[39m\n\u001b[32m 87\u001b[39m \u001b[38;5;129m@classmethod\u001b[39m\n\u001b[32m 88\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mmodel_to_columns\u001b[39m(\u001b[38;5;28mcls\u001b[39m, model_class):\n\u001b[32m 89\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"Convert a Pydantic model class to Ragas API column definitions.\"\"\"\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m90\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mragas_experimental\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mmetric\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mresult\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m MetricResult\n\u001b[32m 92\u001b[39m columns = []\n\u001b[32m 93\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m field_name, field_info \u001b[38;5;129;01min\u001b[39;00m model_class.model_fields.items():\n\u001b[32m 94\u001b[39m \u001b[38;5;66;03m# Get the field's type annotation\u001b[39;00m\n",
- "\u001b[36mFile \u001b[39m\u001b[32m~/workspace/eglabs/ragas_experimental/ragas_experimental/__init__.py:8\u001b[39m\n\u001b[32m 5\u001b[39m __all__ = []\n\u001b[32m 7\u001b[39m \u001b[38;5;66;03m# %% ../nbs/init_module.ipynb 2\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m8\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m.\u001b[39;00m\u001b[34;01mproject\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mcore\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m Project\n\u001b[32m 9\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mragas_experimental\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mmodel\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mnotion_typing\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mnmt\u001b[39;00m\n\u001b[32m 10\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m.\u001b[39;00m\u001b[34;01mmodel\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mnotion_model\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m NotionModel\n",
- "\u001b[36mFile \u001b[39m\u001b[32m~/workspace/eglabs/ragas_experimental/ragas_experimental/project/core.py:18\u001b[39m\n\u001b[32m 16\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m.\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mbackends\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mfactory\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m RagasApiClientFactory\n\u001b[32m 17\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m.\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mbackends\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mragas_api_client\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m RagasApiClient\n\u001b[32m---> \u001b[39m\u001b[32m18\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mragas_experimental\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mtyping\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mrt\u001b[39;00m\n\u001b[32m 19\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m.\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mutils\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m async_to_sync, create_nano_id\n\u001b[32m 20\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m.\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mdataset\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m Dataset\n",
- "\u001b[36mFile \u001b[39m\u001b[32m~/workspace/eglabs/ragas_experimental/ragas_experimental/typing.py:15\u001b[39m\n\u001b[32m 12\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mdatetime\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m datetime, date\n\u001b[32m 13\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01minspect\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m15\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m.\u001b[39;00m\u001b[34;01mmetric\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mresult\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m MetricResult\n\u001b[32m 17\u001b[39m \u001b[38;5;66;03m# %% ../nbs/typing.ipynb 4\u001b[39;00m\n\u001b[32m 18\u001b[39m \u001b[38;5;28;01mclass\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mColumnType\u001b[39;00m(\u001b[38;5;28mstr\u001b[39m, Enum):\n",
- "\u001b[36mFile \u001b[39m\u001b[32m~/workspace/eglabs/ragas_experimental/ragas_experimental/metric/__init__.py:2\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mragas_experimental\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mmetric\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mresult\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m MetricResult\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mragas_experimental\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mmetric\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mbase\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m Metric\n\u001b[32m 3\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mragas_experimental\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mmetric\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mdiscrete\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m DiscreteMetric\n\u001b[32m 4\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mragas_experimental\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mmetric\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mnumeric\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m NumericMetric\n",
- "\u001b[36mFile \u001b[39m\u001b[32m~/workspace/eglabs/ragas_experimental/ragas_experimental/metric/base.py:22\u001b[39m\n\u001b[32m 20\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m.\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m MetricResult\n\u001b[32m 21\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m.\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mllm\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m RagasLLM\n\u001b[32m---> \u001b[39m\u001b[32m22\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m.\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mproject\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mcore\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m Project\n\u001b[32m 23\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m.\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mmodel\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mnotion_model\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m NotionModel\n\u001b[32m 24\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m.\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mprompt\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mdynamic_few_shot\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m DynamicFewShotPrompt\n",
- "\u001b[31mImportError\u001b[39m: cannot import name 'Project' from partially initialized module 'ragas_experimental.project.core' (most likely due to a circular import) (/Users/jjmachan/workspace/eglabs/ragas_experimental/ragas_experimental/project/core.py)"
+ "Testing model_to_columns:\n",
+ "- id (number): {}\n",
+ "- name (longText): {'max_length': 1000}\n",
+ "- is_active (checkbox): {}\n",
+ "- created_at (date): {'include_time': True}\n",
+ "- optional_text (longText): {'max_length': 1000}\n",
+ "- tags (longText): {'max_length': 1000}\n",
+ "- status (select): {'options': [{'name': 'pending'}, {'name': 'active'}, {'name': 'completed'}]}\n",
+ "- score (number): {}\n",
+ "- description (longText): {'max_length': 1000}\n",
+ "- category (select): {'options': [{'name': 'A'}, {'name': 'B'}, {'name': 'C'}]}\n",
+ "- features (longText): {'max_length': 1000}\n"
]
}
],
@@ -650,7 +509,7 @@
"- id: 1\n",
"- name: Test Item 1\n",
"- is_active: True\n",
- "- created_at: 2025-04-14T17:36:24.492518\n",
+ "- created_at: 2025-04-15T17:02:27.298241\n",
"- optional_text: None\n",
"- tags: ['tag1', 'tag2']\n",
"- status: active\n",
@@ -685,11 +544,11 @@
"\n",
"Testing type inference:\n",
"- : number (Required: True)\n",
- "- : text (Required: True)\n",
+ "- : longText (Required: True)\n",
"- : checkbox (Required: True)\n",
"- : date (Required: True)\n",
- "- typing.Optional[str]: text (Required: False)\n",
- "- typing.List[str]: text (Required: True)\n",
+ "- typing.Optional[str]: longText (Required: False)\n",
+ "- typing.List[str]: longText (Required: True)\n",
"- typing.Literal['a', 'b']: select (Required: True)\n",
" - Options: ['a', 'b']\n",
"- typing.List[typing.Literal['x', 'y']]: multiSelect (Required: True)\n",
@@ -744,7 +603,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "[{'id': 'id', 'name': 'id', 'type': 'number', 'settings': {}, 'editable': False}, {'id': 'name', 'name': 'name', 'type': 'text', 'settings': {'max_length': 1000}, 'editable': False}, {'id': 'created_at', 'name': 'created_at', 'type': 'date', 'settings': {'include_time': True}, 'editable': False}]\n"
+ "[{'id': 'id', 'name': 'id', 'type': 'number', 'settings': {}, 'editable': False}, {'id': 'name', 'name': 'name', 'type': 'longText', 'settings': {'max_length': 1000}, 'editable': False}, {'id': 'created_at', 'name': 'created_at', 'type': 'date', 'settings': {'include_time': True}, 'editable': False}]\n"
]
}
],
@@ -818,22 +677,22 @@
"text/plain": [
"[{'id': 'query',\n",
" 'name': 'query',\n",
- " 'type': 'text',\n",
+ " 'type': 'longText',\n",
" 'settings': {'max_length': 1000},\n",
" 'editable': False},\n",
" {'id': 'response',\n",
" 'name': 'response',\n",
- " 'type': 'text',\n",
+ " 'type': 'longText',\n",
" 'settings': {'max_length': 1000},\n",
" 'editable': False},\n",
" {'id': 'score',\n",
" 'name': 'score',\n",
- " 'type': 'text',\n",
+ " 'type': 'longText',\n",
" 'settings': {'max_length': 1000},\n",
" 'editable': True},\n",
" {'id': 'score_reason',\n",
" 'name': 'score_reason',\n",
- " 'type': 'text',\n",
+ " 'type': 'longText',\n",
" 'settings': {'max_length': 1000},\n",
" 'editable': True}]"
]
@@ -872,6 +731,29 @@
"ModelConverter.instance_to_row(m)"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{'data': [{'column_id': 'query', 'data': 'test'},\n",
+ " {'column_id': 'response', 'data': 'test'},\n",
+ " {'column_id': 'score', 'data': 1},\n",
+ " {'column_id': 'score_reason', 'data': 'test'}]}]"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "ModelConverter.instances_to_rows([m])"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
diff --git a/ragas_experimental/_modidx.py b/ragas_experimental/_modidx.py
index ad33045..e58fd07 100644
--- a/ragas_experimental/_modidx.py
+++ b/ragas_experimental/_modidx.py
@@ -95,8 +95,6 @@
'ragas_experimental/backends/notion_backend.py')},
'ragas_experimental.backends.ragas_api_client': { 'ragas_experimental.backends.ragas_api_client.Column': ( 'backends/ragas_api_client.html#column',
'ragas_experimental/backends/ragas_api_client.py'),
- 'ragas_experimental.backends.ragas_api_client.ColumnType': ( 'backends/ragas_api_client.html#columntype',
- 'ragas_experimental/backends/ragas_api_client.py'),
'ragas_experimental.backends.ragas_api_client.RagasApiClient': ( 'backends/ragas_api_client.html#ragasapiclient',
'ragas_experimental/backends/ragas_api_client.py'),
'ragas_experimental.backends.ragas_api_client.RagasApiClient.__init__': ( 'backends/ragas_api_client.html#ragasapiclient.__init__',
@@ -349,6 +347,8 @@
'ragas_experimental/metric/result.py'),
'ragas_experimental.metric.result.MetricResult.__iter__': ( 'metric/result.html#metricresult.__iter__',
'ragas_experimental/metric/result.py'),
+ 'ragas_experimental.metric.result.MetricResult.__json__': ( 'metric/result.html#metricresult.__json__',
+ 'ragas_experimental/metric/result.py'),
'ragas_experimental.metric.result.MetricResult.__le__': ( 'metric/result.html#metricresult.__le__',
'ragas_experimental/metric/result.py'),
'ragas_experimental.metric.result.MetricResult.__len__': ( 'metric/result.html#metricresult.__len__',
@@ -373,8 +373,6 @@
'ragas_experimental/metric/result.py'),
'ragas_experimental.metric.result.MetricResult.__truediv__': ( 'metric/result.html#metricresult.__truediv__',
'ragas_experimental/metric/result.py'),
- 'ragas_experimental.metric.result.MetricResult.model_dump': ( 'metric/result.html#metricresult.model_dump',
- 'ragas_experimental/metric/result.py'),
'ragas_experimental.metric.result.MetricResult.result': ( 'metric/result.html#metricresult.result',
'ragas_experimental/metric/result.py'),
'ragas_experimental.metric.result.MetricResult.to_dict': ( 'metric/result.html#metricresult.to_dict',
@@ -519,6 +517,8 @@
'ragas_experimental/model/pydantic_model.py'),
'ragas_experimental.model.pydantic_model.ExtendedPydanticBaseModel._initialize_column_mapping': ( 'model/pydantic_mode.html#extendedpydanticbasemodel._initialize_column_mapping',
'ragas_experimental/model/pydantic_model.py'),
+ 'ragas_experimental.model.pydantic_model.ExtendedPydanticBaseModel._is_metric_result_field': ( 'model/pydantic_mode.html#extendedpydanticbasemodel._is_metric_result_field',
+ 'ragas_experimental/model/pydantic_model.py'),
'ragas_experimental.model.pydantic_model.ExtendedPydanticBaseModel.get_column_id': ( 'model/pydantic_mode.html#extendedpydanticbasemodel.get_column_id',
'ragas_experimental/model/pydantic_model.py'),
'ragas_experimental.model.pydantic_model.ExtendedPydanticBaseModel.get_db_field_mapping': ( 'model/pydantic_mode.html#extendedpydanticbasemodel.get_db_field_mapping',
diff --git a/ragas_experimental/backends/ragas_api_client.py b/ragas_experimental/backends/ragas_api_client.py
index 2efe3bd..04ce591 100644
--- a/ragas_experimental/backends/ragas_api_client.py
+++ b/ragas_experimental/backends/ragas_api_client.py
@@ -3,7 +3,7 @@
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/backends/ragas_api_client.ipynb.
# %% auto 0
-__all__ = ['DEFAULT_SETTINGS', 'RagasApiClient', 'ColumnType', 'create_nano_id', 'Column', 'RowCell', 'Row']
+__all__ = ['DEFAULT_SETTINGS', 'RagasApiClient', 'create_nano_id', 'Column', 'RowCell', 'Row']
# %% ../../nbs/backends/ragas_api_client.ipynb 4
import httpx
@@ -258,20 +258,9 @@ async def delete_experiment(self: RagasApiClient, project_id: str, experiment_id
# %% ../../nbs/backends/ragas_api_client.ipynb 25
-from enum import Enum
+from ..typing import ColumnType
# %% ../../nbs/backends/ragas_api_client.ipynb 26
-class ColumnType(str, Enum):
- NUMBER = "number"
- TEXT = "text"
- LONG_TEXT = "longText"
- SELECT = "select"
- DATE = "date"
- MULTI_SELECT = "multiSelect"
- CHECKBOX = "checkbox"
- CUSTOM = "custom"
-
-# %% ../../nbs/backends/ragas_api_client.ipynb 27
#---- Dataset Columns ----
@patch
async def list_dataset_columns(
@@ -342,7 +331,7 @@ async def delete_dataset_column(
f"projects/{project_id}/datasets/{dataset_id}/columns/{column_id}"
)
-# %% ../../nbs/backends/ragas_api_client.ipynb 35
+# %% ../../nbs/backends/ragas_api_client.ipynb 34
#---- Dataset Rows ----
@patch
async def list_dataset_rows(
@@ -404,11 +393,11 @@ async def delete_dataset_row(
)
-# %% ../../nbs/backends/ragas_api_client.ipynb 47
+# %% ../../nbs/backends/ragas_api_client.ipynb 46
import uuid
import string
-# %% ../../nbs/backends/ragas_api_client.ipynb 48
+# %% ../../nbs/backends/ragas_api_client.ipynb 47
def create_nano_id(size=12):
# Define characters to use (alphanumeric)
alphabet = string.ascii_letters + string.digits
@@ -425,11 +414,11 @@ def create_nano_id(size=12):
# Pad if necessary and return desired length
return result[:size]
-# %% ../../nbs/backends/ragas_api_client.ipynb 50
+# %% ../../nbs/backends/ragas_api_client.ipynb 49
import uuid
import string
-# %% ../../nbs/backends/ragas_api_client.ipynb 51
+# %% ../../nbs/backends/ragas_api_client.ipynb 50
def create_nano_id(size=12):
# Define characters to use (alphanumeric)
alphabet = string.ascii_letters + string.digits
@@ -446,7 +435,7 @@ def create_nano_id(size=12):
# Pad if necessary and return desired length
return result[:size]
-# %% ../../nbs/backends/ragas_api_client.ipynb 53
+# %% ../../nbs/backends/ragas_api_client.ipynb 52
# Default settings for columns
DEFAULT_SETTINGS = {
"is_required": False,
@@ -469,7 +458,7 @@ class Row(BaseModel):
id: str = Field(default_factory=create_nano_id)
data: t.List[RowCell] = Field(...)
-# %% ../../nbs/backends/ragas_api_client.ipynb 54
+# %% ../../nbs/backends/ragas_api_client.ipynb 53
#---- Resource With Data Helper Methods ----
@patch
async def _create_with_data(
@@ -596,7 +585,7 @@ async def create_dataset_with_data(
"dataset", project_id, name, description, columns, rows, batch_size
)
-# %% ../../nbs/backends/ragas_api_client.ipynb 60
+# %% ../../nbs/backends/ragas_api_client.ipynb 59
#---- Experiment Columns ----
@patch
async def list_experiment_columns(
@@ -727,7 +716,7 @@ async def delete_experiment_row(
f"projects/{project_id}/experiments/{experiment_id}/rows/{row_id}"
)
-# %% ../../nbs/backends/ragas_api_client.ipynb 63
+# %% ../../nbs/backends/ragas_api_client.ipynb 62
@patch
async def create_experiment_with_data(
self: RagasApiClient,
@@ -758,7 +747,7 @@ async def create_experiment_with_data(
"experiment", project_id, name, description, columns, rows, batch_size
)
-# %% ../../nbs/backends/ragas_api_client.ipynb 64
+# %% ../../nbs/backends/ragas_api_client.ipynb 63
#---- Utility Methods ----
@patch
def create_column(
diff --git a/ragas_experimental/dataset.py b/ragas_experimental/dataset.py
index 5a70c52..7f6df1c 100644
--- a/ragas_experimental/dataset.py
+++ b/ragas_experimental/dataset.py
@@ -102,7 +102,10 @@ def __len__(self) -> int:
def __iter__(self) -> t.Iterator[BaseModelType]:
return iter(self._entries)
-# %% ../nbs/dataset.ipynb 16
+# %% ../nbs/dataset.ipynb 18
+import ragas_experimental.typing as rt
+
+# %% ../nbs/dataset.ipynb 19
@patch
def append(self: Dataset, entry: BaseModelType) -> None:
"""Add a new entry to the dataset and sync to Notion."""
@@ -112,12 +115,12 @@ def append(self: Dataset, entry: BaseModelType) -> None:
column_id_map = self.model.__column_mapping__
# create the rows
- row_dict = entry.model_dump()
+ row_dict_converted = rt.ModelConverter.instance_to_row(entry)
row_id = create_nano_id()
row_data = {}
- for key, value in row_dict.items():
- if key in column_id_map:
- row_data[column_id_map[key]] = value
+ for column in row_dict_converted["data"]:
+ if column["column_id"] in column_id_map:
+ row_data[column_id_map[column["column_id"]]] = column["data"]
sync_func = async_to_sync(self._ragas_api_client.create_dataset_row)
response = sync_func(
@@ -131,7 +134,7 @@ def append(self: Dataset, entry: BaseModelType) -> None:
# Update entry with Notion data (like ID)
self._entries.append(entry)
-# %% ../nbs/dataset.ipynb 19
+# %% ../nbs/dataset.ipynb 22
@patch
def pop(self: Dataset, index: int = -1) -> BaseModelType:
"""Remove and return entry at index, sync deletion to Notion."""
@@ -148,7 +151,7 @@ def pop(self: Dataset, index: int = -1) -> BaseModelType:
# Remove from local cache
return self._entries.pop(index)
-# %% ../nbs/dataset.ipynb 22
+# %% ../nbs/dataset.ipynb 26
@patch
def load(self: Dataset) -> None:
"""Load all entries from the backend API."""
@@ -184,7 +187,7 @@ def load(self: Dataset) -> None:
self._entries.append(entry)
-# %% ../nbs/dataset.ipynb 24
+# %% ../nbs/dataset.ipynb 28
@patch
def load_as_dicts(self: Dataset) -> t.List[t.Dict]:
"""Load all entries as dictionaries."""
@@ -210,7 +213,7 @@ def load_as_dicts(self: Dataset) -> t.List[t.Dict]:
return result
-# %% ../nbs/dataset.ipynb 26
+# %% ../nbs/dataset.ipynb 30
@patch
def to_pandas(self: Dataset) -> "pd.DataFrame":
"""Convert dataset to pandas DataFrame."""
@@ -224,7 +227,7 @@ def to_pandas(self: Dataset) -> "pd.DataFrame":
data = [entry.model_dump() for entry in self._entries]
return pd.DataFrame(data)
-# %% ../nbs/dataset.ipynb 28
+# %% ../nbs/dataset.ipynb 32
@patch
def save(self: Dataset, item: BaseModelType) -> None:
"""Save changes to an item to the backend."""
@@ -248,12 +251,12 @@ def save(self: Dataset, item: BaseModelType) -> None:
# Get column mapping and prepare data
column_id_map = self.model.__column_mapping__
- row_dict = item.model_dump()
+ row_dict = rt.ModelConverter.instance_to_row(item)["data"]
row_data = {}
- for key, value in row_dict.items():
- if key in column_id_map:
- row_data[column_id_map[key]] = value
+ for column in row_dict:
+ if column["column_id"] in column_id_map:
+ row_data[column_id_map[column["column_id"]]] = column["data"]
# Update in backend
sync_func = async_to_sync(self._ragas_api_client.update_dataset_row)
@@ -272,7 +275,7 @@ def save(self: Dataset, item: BaseModelType) -> None:
self._entries[i] = item
break
-# %% ../nbs/dataset.ipynb 32
+# %% ../nbs/dataset.ipynb 36
@patch
def get(self: Dataset, field_value: str, field_name: str = "_row_id") -> t.Optional[BaseModelType]:
"""Get an entry by field value.
diff --git a/ragas_experimental/metric/result.py b/ragas_experimental/metric/result.py
index 5a6dc22..10887d9 100644
--- a/ragas_experimental/metric/result.py
+++ b/ragas_experimental/metric/result.py
@@ -188,18 +188,50 @@ def validate(cls: MetricResult, value: t.Any, info: ValidationInfo):
return value
return MetricResult(result=value)
-# Add Pydantic compatibility methods
+@patch
+def __json__(self: MetricResult):
+ """Return data for JSON serialization.
+
+ This method is used by json.dumps and other JSON serializers
+ to convert MetricResult to a JSON-compatible format.
+ """
+ return {
+ "result": self._result,
+ "reason": self.reason,
+ }
+
@patch(cls_method=True)
def __get_pydantic_core_schema__(
cls: MetricResult,
_source_type: t.Any,
_handler: GetCoreSchemaHandler
) -> core_schema.CoreSchema:
- """Generate a Pydantic core schema for MetricResult."""
- return core_schema.with_info_plain_validator_function(cls.validate)
-
-
-@patch
-def model_dump(self: MetricResult):
- """Support Pydantic's model_dump method."""
- return self.to_dict()
+ """Generate a Pydantic core schema for MetricResult.
+
+ This custom schema handles different serialization behaviors:
+ - For model_dump(): Returns the original MetricResult instance
+ - For model_dump_json(): Converts to a JSON-compatible dict using __json__
+ """
+ def serializer_function(instance, info):
+ """Handle different serialization modes for MetricResult."""
+ # For JSON serialization (model_dump_json), use __json__ method
+ if getattr(info, 'mode', None) == 'json':
+ return instance.__json__()
+ # For Python serialization (model_dump), return the instance itself
+ return instance
+
+ return core_schema.union_schema([
+ # First schema: handles validation of MetricResult instances
+ core_schema.is_instance_schema(MetricResult),
+
+ # Second schema: handles validation of other values and conversion to MetricResult
+ core_schema.chain_schema([
+ core_schema.any_schema(),
+ core_schema.no_info_plain_validator_function(
+ lambda value: MetricResult(result=value) if not isinstance(value, MetricResult) else value
+ ),
+ ]),
+ ], serialization=core_schema.plain_serializer_function_ser_schema(
+ serializer_function,
+ info_arg=True # Explicitly specify that we're using the info argument
+ ))
diff --git a/ragas_experimental/model/pydantic_model.py b/ragas_experimental/model/pydantic_model.py
index c3b3eca..4fc9875 100644
--- a/ragas_experimental/model/pydantic_model.py
+++ b/ragas_experimental/model/pydantic_model.py
@@ -44,6 +44,24 @@ def _initialize_column_mapping(cls):
column_id = field_name
cls.__column_mapping__[field_name] = column_id
+
+ # check if the field is a MetricResult
+ if cls._is_metric_result_field(field_info.annotation):
+ # add additional mapping for the metric result
+ reason_field_name = f"{field_name}_reason"
+ reason_column_id = f"{column_id}_reason"
+ cls.__column_mapping__[reason_field_name] = reason_column_id
+
+ @staticmethod
+ def _is_metric_result_field(annotation):
+ """Check if a field annotation represents a MetricResult."""
+ # Direct import of MetricResult
+ from ragas_experimental.metric.result import MetricResult
+
+ # Check if annotation is or references MetricResult
+ return (annotation is MetricResult or
+ (hasattr(annotation, "__origin__") and annotation.__origin__ is MetricResult) or
+ (hasattr(annotation, "__class__") and annotation.__class__ is MetricResult))
@classmethod
def get_column_id(cls, field_name: str) -> str:
diff --git a/ragas_experimental/typing.py b/ragas_experimental/typing.py
index f537ffe..fb39874 100644
--- a/ragas_experimental/typing.py
+++ b/ragas_experimental/typing.py
@@ -18,7 +18,7 @@
class ColumnType(str, Enum):
"""Column types supported by the Ragas API."""
NUMBER = "number"
- TEXT = "text"
+ TEXT = "longText"
SELECT = "select"
MULTI_SELECT = "multiSelect"
CHECKBOX = "checkbox"
@@ -301,131 +301,3 @@ def instances_to_rows(cls, instances, model_class=None):
model_class = instances[0].__class__
return [cls.instance_to_row(instance, model_class) for instance in instances]
-
-# %% ../nbs/typing.ipynb 15
-# class ModelConverter:
-# """Convert Pydantic models to Ragas API columns and rows."""
-
-# @staticmethod
-# def infer_field_type(annotation, field_info=None):
-# """Infer field type from Python type annotation."""
-# # Check for Annotated with our custom metadata
-# origin = t.get_origin(annotation)
-# args = t.get_args(annotation)
-
-# # If this is an Annotated field with our metadata
-# if origin is t.Annotated and len(args) > 1:
-# for arg in args[1:]:
-# if isinstance(arg, FieldMeta):
-# return arg
-
-# # If no field metadata found, infer from the base type
-# return ModelConverter.infer_field_type(args[0], field_info)
-
-# # Handle Optional, List, etc.
-# if origin is t.Union:
-# if type(None) in args:
-# # This is Optional[T]
-# non_none_args = [arg for arg in args if arg is not type(None)]
-# if len(non_none_args) == 1:
-# # Get the field type of the non-None arg
-# field_meta = ModelConverter.infer_field_type(non_none_args[0], field_info)
-# field_meta.required = False
-# return field_meta
-
-# # Handle List and array types
-# # NOTE: here we are converting lists to strings, except for literal types
-# if origin is list or origin is t.List:
-# if len(args) > 0:
-# # Check if it's a list of literals
-# if t.get_origin(args[0]) is t.Literal:
-# literal_options = t.get_args(args[0])
-# return MultiSelect(options=list(literal_options))
-# # Otherwise just a regular list
-# return Text() # Default to Text for lists
-
-# # Handle Literal
-# if origin is t.Literal:
-# return Select(options=list(args))
-
-# # Basic type handling
-# if annotation is str:
-# return Text()
-# elif annotation is int or annotation is float:
-# return Number()
-# elif annotation is bool:
-# return Checkbox()
-# elif annotation is datetime or annotation is date:
-# return Date(include_time=annotation is datetime)
-
-# # Default to Text for complex or unknown types
-# return Text()
-
-# @classmethod
-# def model_to_columns(cls, model_class):
-# """Convert a Pydantic model class to Ragas API column definitions."""
-# columns = []
-# for field_name, field_info in model_class.model_fields.items():
-# # Get the field's type annotation
-# annotation = field_info.annotation
-
-# # Try to get field metadata
-# field_meta = cls.infer_field_type(annotation, field_info)
-
-# # Create column definition
-# column = {
-# "id": field_name,
-# "name": field_name,
-# "type": field_meta.type.value,
-# "settings": field_meta.settings.copy()
-# }
-
-# columns.append(column)
-
-# return columns
-
-# @classmethod
-# def instance_to_row(cls, instance, model_class=None):
-# """Convert a Pydantic model instance to a Ragas API row."""
-# if model_class is None:
-# model_class = instance.__class__
-
-# row_cells = []
-# model_data = instance.model_dump()
-
-# for field_name, field_info in model_class.model_fields.items():
-# if field_name in model_data:
-# value = model_data[field_name]
-# # Process value based on field type
-# annotation = field_info.annotation
-# field_meta = cls.infer_field_type(annotation, field_info)
-
-# # Special handling for various types
-# if field_meta.type == ColumnType.MULTI_SELECT and isinstance(value, list):
-# # Convert list to string format accepted by API
-# processed_value = value
-# elif field_meta.type == ColumnType.DATE and isinstance(value, (datetime, date)):
-# # Format date as string
-# processed_value = value.isoformat()
-# else:
-# processed_value = value
-
-# row_cells.append({
-# "column_id": field_name,
-# "data": processed_value
-# })
-
-# return {
-# "data": row_cells
-# }
-
-# @classmethod
-# def instances_to_rows(cls, instances, model_class=None):
-# """Convert multiple Pydantic model instances to Ragas API rows."""
-# if not instances:
-# return []
-
-# if model_class is None and instances:
-# model_class = instances[0].__class__
-
-# return [cls.instance_to_row(instance, model_class) for instance in instances]