diff --git a/crates/reasondb-core/src/model/table.rs b/crates/reasondb-core/src/model/table.rs index 737be42..4c60e6f 100644 --- a/crates/reasondb-core/src/model/table.rs +++ b/crates/reasondb-core/src/model/table.rs @@ -66,6 +66,35 @@ pub struct Table { #[serde(with = "json_metadata")] pub metadata: HashMap, + /// Domain-specific vocabulary and terminology for this table. + /// + /// Used by the LLM reasoning engine to understand domain-specific terms + /// when traversing and interpreting documents in this table. + /// + /// Examples: `["contract", "indemnification", "arbitration"]` + #[serde(default)] + pub domain_vocab: Option>, + + /// Contextual description for LLM reasoning. + /// + /// Provides background context about what this table contains and how + /// documents should be interpreted during semantic search and reasoning. + #[serde(default)] + pub context: Option, + + /// Instructions for LLM query and reasoning behavior on this table. + /// + /// Custom directives that guide how the LLM should answer questions + /// or traverse documents within this table. + #[serde(default)] + pub instructions: Option, + + /// Classification tags for this table. + /// + /// Used for grouping, filtering, and discovery of tables. + #[serde(default)] + pub tags: Option>, + /// Number of documents in this table pub document_count: usize, @@ -96,6 +125,10 @@ impl Table { slug, description: None, metadata: HashMap::new(), + domain_vocab: None, + context: None, + instructions: None, + tags: None, document_count: 0, total_nodes: 0, created_at: now, @@ -113,6 +146,10 @@ impl Table { slug, description: None, metadata: HashMap::new(), + domain_vocab: None, + context: None, + instructions: None, + tags: None, document_count: 0, total_nodes: 0, created_at: now, diff --git a/crates/reasondb-server/src/openapi.rs b/crates/reasondb-server/src/openapi.rs index 4dee07c..11bd904 100644 --- a/crates/reasondb-server/src/openapi.rs +++ b/crates/reasondb-server/src/openapi.rs @@ -37,8 +37,10 @@ use crate::routes::{documents, ingest, search}; search::search, documents::list_documents, documents::get_document, + documents::update_document, documents::delete_document, documents::get_document_nodes, + documents::update_node, documents::get_document_tree, ), components( @@ -60,7 +62,11 @@ use crate::routes::{documents, ingest, search}; // Documents documents::DocumentSummary, documents::DocumentDetail, + documents::UpdateDocumentRequest, documents::NodeSummary, + documents::UpdateNodeMetadataRequest, + documents::UpdateNodeRequest, + documents::NodeDetail, documents::TreeNode, // Errors crate::error::ErrorResponse, diff --git a/crates/reasondb-server/src/routes/documents.rs b/crates/reasondb-server/src/routes/documents.rs index b985363..348bcd3 100644 --- a/crates/reasondb-server/src/routes/documents.rs +++ b/crates/reasondb-server/src/routes/documents.rs @@ -87,6 +87,110 @@ pub struct MoveDocumentRequest { pub table_id: String, } +/// Partial update for node metadata fields +#[derive(Debug, Deserialize, ToSchema)] +pub struct UpdateNodeMetadataRequest { + /// Page number in the source document (optional) + #[schema(example = 3)] + pub page_number: Option, + + /// Section type (e.g. "chapter", "section", "paragraph") (optional) + #[schema(example = "section")] + pub section_type: Option, + + /// Confidence score assigned during ingestion (optional) + #[schema(example = 0.95)] + pub confidence_score: Option, + + /// Custom key-value attributes to merge into the node (optional) + #[schema(example = json!({"source": "manual", "reviewed": "true"}))] + pub attributes: Option>, + + /// IDs of sibling nodes this node explicitly cross-references (optional) + #[schema(example = json!(["node_abc", "node_def"]))] + pub cross_ref_node_ids: Option>, +} + +/// Request to update a document node +#[derive(Debug, Deserialize, ToSchema)] +pub struct UpdateNodeRequest { + /// Updated title (optional) + #[schema(example = "Chapter 3: Neural Networks — Revised")] + pub title: Option, + + /// Updated LLM summary for tree traversal (optional) + #[schema( + example = "This chapter covers neural network fundamentals including backpropagation." + )] + pub summary: Option, + + /// Updated leaf content (optional, only meaningful on leaf nodes) + #[schema( + example = "Neural networks are computing systems loosely inspired by biological neural networks..." + )] + pub content: Option, + + /// Updated path to associated image (optional) + #[schema(example = "/uploads/figure-3-1.png")] + pub image_path: Option, + + /// Metadata fields to merge/update (optional) + pub metadata: Option, +} + +/// Response for a single updated node +#[derive(Debug, Serialize, ToSchema)] +pub struct NodeDetail { + /// Unique node ID + #[schema(example = "node_xyz789")] + pub id: String, + /// Parent document ID + #[schema(example = "doc_abc123")] + pub document_id: String, + /// Node title + #[schema(example = "Chapter 3: Neural Networks")] + pub title: String, + /// LLM-generated summary + #[schema(example = "This chapter covers neural network fundamentals...")] + pub summary: String, + /// Depth in the tree (0 = root) + #[schema(example = 1)] + pub depth: u8, + /// Whether this is a leaf node + #[schema(example = false)] + pub is_leaf: bool, + /// Actual content (only present for leaf nodes) + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, + /// Path to associated image + #[serde(skip_serializing_if = "Option::is_none")] + pub image_path: Option, + /// Parent node ID + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_id: Option, + /// Child node IDs + pub children_ids: Vec, + /// Page number in source document + #[serde(skip_serializing_if = "Option::is_none")] + pub page_number: Option, + /// Section type + #[serde(skip_serializing_if = "Option::is_none")] + pub section_type: Option, + /// Confidence score + #[serde(skip_serializing_if = "Option::is_none")] + pub confidence_score: Option, + /// Custom attributes + pub attributes: std::collections::HashMap, + /// Cross-referenced sibling node IDs + pub cross_ref_node_ids: Vec, + /// Creation timestamp (ISO 8601) + #[schema(example = "2024-01-15T10:30:00Z")] + pub created_at: String, + /// Last update timestamp (ISO 8601) + #[schema(example = "2024-01-15T10:35:00Z")] + pub updated_at: String, +} + /// Full document details #[derive(Debug, Serialize, ToSchema)] pub struct DocumentDetail { @@ -414,6 +518,112 @@ pub async fn get_document_nodes( Ok(Json(summaries)) } +/// Update a document node +/// +/// Partially updates a single node identified by its document and node IDs. +/// Only provided fields are changed. The `metadata.attributes` map is merged +/// key-by-key (not replaced wholesale). `cross_ref_node_ids` is replaced when provided. +#[utoipa::path( + patch, + path = "/v1/documents/{id}/nodes/{node_id}", + tag = "documents", + params( + ("id" = String, Path, description = "Document ID"), + ("node_id" = String, Path, description = "Node ID"), + ), + request_body = UpdateNodeRequest, + responses( + (status = 200, description = "Node updated", body = NodeDetail), + (status = 404, description = "Document or node not found", body = ErrorResponse), + (status = 500, description = "Storage error", body = ErrorResponse), + ) +)] +pub async fn update_node( + State(state): State>>, + Path((doc_id, node_id)): Path<(String, String)>, + Json(request): Json, +) -> ApiResult> { + info!("Updating node {} in document {}", node_id, doc_id); + + // Verify document exists + let _doc = state + .store + .get_document(&doc_id) + .map_err(ApiError::from)? + .ok_or_else(|| ApiError::NotFound(format!("Document not found: {}", doc_id)))?; + + let mut node = state + .store + .get_node(&node_id) + .map_err(ApiError::from)? + .ok_or_else(|| ApiError::NotFound(format!("Node not found: {}", node_id)))?; + + if node.document_id != doc_id { + return Err(ApiError::NotFound(format!( + "Node {} does not belong to document {}", + node_id, doc_id + ))); + } + + if let Some(title) = request.title { + node.title = title; + } + if let Some(summary) = request.summary { + node.summary = summary; + } + if let Some(content) = request.content { + node.content = Some(content); + } + if let Some(image_path) = request.image_path { + node.image_path = Some(image_path); + } + + if let Some(meta) = request.metadata { + if let Some(page_number) = meta.page_number { + node.metadata.page_number = Some(page_number); + } + if let Some(section_type) = meta.section_type { + node.metadata.section_type = Some(section_type); + } + if let Some(confidence_score) = meta.confidence_score { + node.metadata.confidence_score = Some(confidence_score); + } + if let Some(attributes) = meta.attributes { + for (k, v) in attributes { + node.metadata.attributes.insert(k, v); + } + } + if let Some(cross_ref_node_ids) = meta.cross_ref_node_ids { + node.metadata.cross_ref_node_ids = cross_ref_node_ids; + } + } + + node.updated_at = Utc::now(); + + state.store.update_node(&node).map_err(ApiError::from)?; + + let is_leaf = node.is_leaf(); + Ok(Json(NodeDetail { + id: node.id, + document_id: node.document_id, + title: node.title, + summary: node.summary, + depth: node.depth, + is_leaf, + content: node.content, + image_path: node.image_path, + parent_id: node.parent_id, + children_ids: node.children_ids, + page_number: node.metadata.page_number, + section_type: node.metadata.section_type, + confidence_score: node.metadata.confidence_score, + attributes: node.metadata.attributes, + cross_ref_node_ids: node.metadata.cross_ref_node_ids, + created_at: node.created_at.to_rfc3339(), + updated_at: node.updated_at.to_rfc3339(), + })) +} + /// Get document as tree structure /// /// Returns the complete hierarchical tree structure with nested children. diff --git a/crates/reasondb-server/src/routes/mod.rs b/crates/reasondb-server/src/routes/mod.rs index 496a1ea..20f2b42 100644 --- a/crates/reasondb-server/src/routes/mod.rs +++ b/crates/reasondb-server/src/routes/mod.rs @@ -105,6 +105,10 @@ fn v1_routes( "/documents/:id/nodes", get(documents::get_document_nodes::), ) + .route( + "/documents/:id/nodes/:node_id", + patch(documents::update_node::), + ) .route( "/documents/:id/tree", get(documents::get_document_tree::), diff --git a/crates/reasondb-server/src/routes/tables.rs b/crates/reasondb-server/src/routes/tables.rs index 34568f5..8ba2fe3 100644 --- a/crates/reasondb-server/src/routes/tables.rs +++ b/crates/reasondb-server/src/routes/tables.rs @@ -6,6 +6,7 @@ use axum::{ extract::{Path, State}, Json, }; +use chrono::Utc; use reasondb_core::{llm::ReasoningEngine, Table}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -52,6 +53,22 @@ pub struct UpdateTableRequest { /// Metadata to merge (optional) #[serde(default)] pub metadata: Option>, + + /// Domain-specific vocabulary and terminology for LLM reasoning (optional) + #[schema(example = json!(["contract", "indemnification", "arbitration"]))] + pub domain_vocab: Option>, + + /// Contextual description for LLM reasoning (optional) + #[schema(example = "This table contains legal contracts and agreements for US jurisdictions.")] + pub context: Option, + + /// Instructions for LLM query and reasoning behavior on this table (optional) + #[schema(example = "Always cite clause numbers when referencing contract terms.")] + pub instructions: Option, + + /// Classification tags for this table (optional) + #[schema(example = json!(["legal", "contracts", "us"]))] + pub tags: Option>, } /// Response containing table details @@ -72,6 +89,22 @@ pub struct TableResponse { /// Custom metadata pub metadata: HashMap, + /// Domain-specific vocabulary and terminology for LLM reasoning + #[serde(skip_serializing_if = "Option::is_none")] + pub domain_vocab: Option>, + + /// Contextual description for LLM reasoning + #[serde(skip_serializing_if = "Option::is_none")] + pub context: Option, + + /// Instructions for LLM query and reasoning behavior + #[serde(skip_serializing_if = "Option::is_none")] + pub instructions: Option, + + /// Classification tags for this table + #[serde(skip_serializing_if = "Option::is_none")] + pub tags: Option>, + /// Number of documents in this table #[schema(example = 15)] pub document_count: usize, @@ -96,6 +129,10 @@ impl From<&Table> for TableResponse { name: table.name.clone(), description: table.description.clone(), metadata: table.metadata.clone(), + domain_vocab: table.domain_vocab.clone(), + context: table.context.clone(), + instructions: table.instructions.clone(), + tags: table.tags.clone(), document_count: table.document_count, total_nodes: table.total_nodes, created_at: table.created_at.to_rfc3339(), @@ -369,6 +406,24 @@ pub async fn update_table( } } + if let Some(domain_vocab) = request.domain_vocab { + table.domain_vocab = Some(domain_vocab); + } + + if let Some(context) = request.context { + table.context = Some(context); + } + + if let Some(instructions) = request.instructions { + table.instructions = Some(instructions); + } + + if let Some(tags) = request.tags { + table.tags = Some(tags); + } + + table.updated_at = Utc::now(); + state .store .update_table(&table) diff --git a/docs/api-reference/openapi.json b/docs/api-reference/openapi.json index 34f5e22..d52f835 100644 --- a/docs/api-reference/openapi.json +++ b/docs/api-reference/openapi.json @@ -572,6 +572,78 @@ } }, "/v1/documents/{id}": { + "patch": { + "summary": "Update a document", + "description": "Partially updates a document's title, table assignment, tags, or metadata. Only provided fields are changed. `metadata` is merged key-by-key, not replaced wholesale.", + "operationId": "updateDocument", + "tags": [ + "documents" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Document ID", + "schema": { + "type": "string", + "example": "doc_abc123" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateDocumentRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Document updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "updated": { + "type": "boolean", + "example": true + }, + "document_id": { + "type": "string", + "example": "doc_abc123" + } + } + } + } + } + }, + "404": { + "description": "Document not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Storage error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, "get": { "summary": "Get document details", "description": "Returns full details for a specific document including metadata and tree info.", @@ -744,6 +816,80 @@ } } }, + "/v1/documents/{id}/nodes/{node_id}": { + "patch": { + "summary": "Update a document node", + "description": "Partially updates a single node by its document and node IDs. Only provided fields are changed. `metadata.attributes` is merged key-by-key. `cross_ref_node_ids` is replaced when provided.", + "operationId": "updateNode", + "tags": [ + "documents" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Document ID", + "schema": { + "type": "string", + "example": "doc_abc123" + } + }, + { + "name": "node_id", + "in": "path", + "required": true, + "description": "Node ID", + "schema": { + "type": "string", + "example": "node_xyz789" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateNodeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Node updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NodeDetail" + } + } + } + }, + "404": { + "description": "Document or node not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Storage error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, "/v1/documents/{id}/tree": { "get": { "summary": "Get document as tree structure", @@ -1119,8 +1265,30 @@ }, "metadata": { "type": "object", - "description": "Metadata to merge", + "description": "Metadata to merge (key-by-key, not replaced wholesale)", "additionalProperties": true + }, + "domain_vocab": { + "type": "array", + "items": { "type": "string" }, + "description": "Domain-specific vocabulary and terminology used by the LLM reasoning engine to interpret documents in this table.", + "example": ["contract", "indemnification", "arbitration"] + }, + "context": { + "type": "string", + "description": "Contextual description for LLM reasoning. Provides background about what this table contains and how documents should be interpreted.", + "example": "This table contains legal contracts and agreements for US jurisdictions." + }, + "instructions": { + "type": "string", + "description": "Custom directives that guide how the LLM should answer questions or traverse documents within this table.", + "example": "Always cite clause numbers when referencing contract terms." + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "description": "Classification tags for grouping and discovery of this table.", + "example": ["legal", "contracts", "us"] } } }, @@ -1158,6 +1326,28 @@ "description": "Custom metadata", "additionalProperties": true }, + "domain_vocab": { + "type": "array", + "items": { "type": "string" }, + "description": "Domain-specific vocabulary for LLM reasoning", + "example": ["contract", "indemnification", "arbitration"] + }, + "context": { + "type": "string", + "description": "Contextual description for LLM reasoning", + "example": "This table contains legal contracts for US jurisdictions." + }, + "instructions": { + "type": "string", + "description": "Custom LLM query and reasoning directives", + "example": "Always cite clause numbers when referencing contract terms." + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "description": "Classification tags for this table", + "example": ["legal", "contracts", "us"] + }, "document_count": { "type": "integer", "description": "Number of documents in this table", @@ -2030,6 +2220,193 @@ "example": "2024-01-15T10:30:05Z" } } + }, + "UpdateDocumentRequest": { + "type": "object", + "description": "Request body for updating a document. All fields are optional; only provided fields are changed.", + "properties": { + "title": { + "type": "string", + "description": "Updated document title", + "example": "NDA Agreement - Updated" + }, + "table_id": { + "type": "string", + "description": "Move document to a different table", + "example": "tbl_archived" + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "description": "Document tags (replaces existing tags)", + "example": ["nda", "archived"] + }, + "metadata": { + "type": "object", + "description": "Metadata to merge (key-by-key, not replaced wholesale)", + "additionalProperties": true, + "example": { "reviewed_by": "Legal Team", "status": "archived" } + } + } + }, + "UpdateNodeMetadataRequest": { + "type": "object", + "description": "Partial update for node metadata fields. All fields are optional.", + "properties": { + "page_number": { + "type": "integer", + "description": "Page number in the source document", + "example": 3 + }, + "section_type": { + "type": "string", + "description": "Section type (e.g. 'chapter', 'section', 'paragraph')", + "example": "section" + }, + "confidence_score": { + "type": "number", + "format": "float", + "description": "Confidence score assigned during ingestion", + "example": 0.95 + }, + "attributes": { + "type": "object", + "description": "Custom key-value attributes to merge into the node (string values only)", + "additionalProperties": { "type": "string" }, + "example": { "source": "manual", "reviewed": "true" } + }, + "cross_ref_node_ids": { + "type": "array", + "items": { "type": "string" }, + "description": "IDs of sibling nodes this node explicitly cross-references (replaces existing list)", + "example": ["node_abc", "node_def"] + } + } + }, + "UpdateNodeRequest": { + "type": "object", + "description": "Request body for updating a document node. All fields are optional; only provided fields are changed.", + "properties": { + "title": { + "type": "string", + "description": "Updated node title", + "example": "Chapter 3: Neural Networks — Revised" + }, + "summary": { + "type": "string", + "description": "Updated LLM summary used during tree traversal", + "example": "This chapter covers neural network fundamentals including backpropagation." + }, + "content": { + "type": "string", + "description": "Updated leaf content (only meaningful for leaf nodes)", + "example": "Neural networks are computing systems loosely inspired by biological neural networks..." + }, + "image_path": { + "type": "string", + "description": "Updated path to associated image for vision-enabled reasoning", + "example": "/uploads/figure-3-1.png" + }, + "metadata": { + "$ref": "#/components/schemas/UpdateNodeMetadataRequest" + } + } + }, + "NodeDetail": { + "type": "object", + "description": "Full details for a single document node.", + "properties": { + "id": { + "type": "string", + "description": "Unique node ID", + "example": "node_xyz789" + }, + "document_id": { + "type": "string", + "description": "Parent document ID", + "example": "doc_abc123" + }, + "title": { + "type": "string", + "description": "Node title", + "example": "Chapter 3: Neural Networks" + }, + "summary": { + "type": "string", + "description": "LLM-generated summary used for tree traversal", + "example": "This chapter covers neural network fundamentals..." + }, + "depth": { + "type": "integer", + "description": "Depth in the tree (0 = root)", + "example": 1 + }, + "is_leaf": { + "type": "boolean", + "description": "Whether this is a leaf node (no children)", + "example": false + }, + "content": { + "type": "string", + "description": "Actual content (only present for leaf nodes)" + }, + "image_path": { + "type": "string", + "description": "Path to associated image for vision-enabled reasoning", + "example": "/uploads/figure-3-1.png" + }, + "parent_id": { + "type": "string", + "description": "Parent node ID (null for root nodes)", + "example": "node_root_abc" + }, + "children_ids": { + "type": "array", + "items": { "type": "string" }, + "description": "IDs of child nodes", + "example": ["node_child1", "node_child2"] + }, + "page_number": { + "type": "integer", + "description": "Page number in the source document", + "example": 3 + }, + "section_type": { + "type": "string", + "description": "Section type (e.g. 'chapter', 'section', 'paragraph')", + "example": "chapter" + }, + "confidence_score": { + "type": "number", + "format": "float", + "description": "Confidence score assigned during ingestion", + "example": 0.95 + }, + "attributes": { + "type": "object", + "description": "Custom key-value attributes (string values)", + "additionalProperties": { "type": "string" }, + "example": { "source": "manual" } + }, + "cross_ref_node_ids": { + "type": "array", + "items": { "type": "string" }, + "description": "IDs of sibling nodes this node cross-references", + "example": [] + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Node creation timestamp", + "example": "2024-01-15T10:30:00Z" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Last update timestamp", + "example": "2024-01-15T10:35:00Z" + } + } } }, "securitySchemes": { diff --git a/docs/guides/tables.mdx b/docs/guides/tables.mdx index b26ef6b..e2d4839 100644 --- a/docs/guides/tables.mdx +++ b/docs/guides/tables.mdx @@ -46,6 +46,20 @@ Response: } ``` +You can also set LLM context fields at creation time: + +```bash +curl -X POST http://localhost:4444/v1/tables \ + -H "Content-Type: application/json" \ + -d '{ + "name": "legal_contracts", + "description": "All legal documents and contracts", + "metadata": { "department": "legal" } + }' +``` + +Use `PATCH /v1/tables/:id` to add `domain_vocab`, `context`, and `instructions` after creation. + ## Table Schema | Field | Type | Description | @@ -54,6 +68,10 @@ Response: | `name` | string | Human-readable name (required) | | `description` | string | Optional description | | `metadata` | object | Custom key-value metadata | +| `domain_vocab` | string[] | Domain-specific vocabulary for LLM reasoning | +| `context` | string | Contextual description for LLM reasoning | +| `instructions` | string | Custom LLM query/reasoning directives | +| `tags` | string[] | Classification tags for grouping and discovery | | `document_count` | int | Number of documents | | `total_nodes` | int | Total nodes across all documents | | `created_at` | datetime | Creation timestamp | @@ -86,7 +104,7 @@ Returns full table information including metadata. ## Updating Tables -Update name, description, or metadata: +Update name, description, metadata, or LLM context fields: ```bash curl -X PATCH http://localhost:4444/v1/tables/tbl_abc123 \ @@ -104,6 +122,32 @@ curl -X PATCH http://localhost:4444/v1/tables/tbl_abc123 \ Metadata updates are merged, not replaced. New keys are added, existing keys are updated. +### Configuring LLM Context + +You can provide domain-specific context to improve the quality of `REASON` queries on a table. These fields are passed to the LLM during tree traversal to help it understand the content and answer questions more accurately. + +```bash +curl -X PATCH http://localhost:4444/v1/tables/tbl_abc123 \ + -H "Content-Type: application/json" \ + -d '{ + "domain_vocab": ["indemnification", "arbitration", "force majeure", "jurisdiction"], + "context": "This table contains US commercial contracts and NDAs. Documents follow standard legal formatting with numbered clauses.", + "instructions": "Always cite specific clause numbers when referencing contract terms. Distinguish between obligations of each party.", + "tags": ["legal", "contracts", "us"] + }' +``` + +| Field | Purpose | +|-------|---------| +| `domain_vocab` | Key terms the LLM should recognize as domain-specific | +| `context` | Background about the table's content and document structure | +| `instructions` | Behavioral directives for how the LLM should answer queries | +| `tags` | Labels for organizing and filtering tables | + + +Setting `context` and `domain_vocab` is especially useful when your documents use specialized terminology or when you want more consistent reasoning behavior across queries. + + ## Deleting Tables ### Move Documents to Default