use crate::types::describe::{FieldType, SObjectDescribe};
use std::fmt::Write;
#[derive(Debug, Clone)]
pub struct LlmContextOptions {
pub include_labels: bool,
pub include_relationships: bool,
pub custom_fields_only: bool,
}
impl Default for LlmContextOptions {
fn default() -> Self {
Self {
include_labels: true,
include_relationships: true,
custom_fields_only: false,
}
}
}
#[must_use]
pub fn generate_llm_context(describe: &SObjectDescribe, options: &LlmContextOptions) -> String {
let mut context = String::with_capacity(1024);
let _ = writeln!(
context,
"SObject: {} (Custom: {}, Label: {})",
describe.name, describe.custom, describe.label
);
let _ = writeln!(context, "Fields:");
let mut sorted_fields = describe.fields.clone();
sorted_fields.sort_by(|a, b| crate::schema::cmp_field_names(&a.name, &b.name));
for field in sorted_fields {
if options.custom_fields_only && !field.custom {
continue;
}
let type_str = match field.type_ {
FieldType::String => "string",
FieldType::Id => "id",
FieldType::Reference => "reference",
FieldType::Int => "int",
FieldType::Double => "double",
FieldType::Boolean => "boolean",
FieldType::Date => "date",
FieldType::Datetime => "datetime",
FieldType::Picklist => "picklist",
FieldType::Multipicklist => "multipicklist",
FieldType::Currency => "currency",
FieldType::Email => "email",
FieldType::Phone => "phone",
FieldType::Url => "url",
FieldType::Textarea => "textarea",
FieldType::Location => "location",
FieldType::Address => "address",
FieldType::Base64 => "base64",
FieldType::Combobox => "combobox",
FieldType::Encryptedstring => "encrypted",
FieldType::Datacategorygroupreference => "datacategory",
FieldType::Percent => "percent",
FieldType::Time => "time",
FieldType::AnyType => "any",
};
let mut modifiers_str = String::new();
let mut has_mod = false;
let mut add_mod = |m: &str| {
if has_mod {
modifiers_str.push(',');
} else {
modifiers_str.push_str(" [");
has_mod = true;
}
modifiers_str.push_str(m);
};
if !field.nillable {
add_mod("req");
}
if field.unique {
add_mod("uniq");
}
if field.external_id {
add_mod("ext_id");
}
if field.calculated {
add_mod("formula");
}
if has_mod {
modifiers_str.push(']');
}
let label_str = if options.include_labels {
format!(" // {}", field.label)
} else {
String::new()
};
if options.include_relationships && field.type_ == FieldType::Reference {
let targets = field.reference_to.join(",");
let _ = writeln!(
context,
" - {}: {} -> {}{}{}",
field.name, type_str, targets, modifiers_str, label_str
);
} else {
let _ = writeln!(
context,
" - {}: {}{}{}",
field.name, type_str, modifiers_str, label_str
);
}
}
if options.include_relationships && !describe.child_relationships.is_empty() {
let _ = writeln!(context, "Child Relationships:");
for rel in &describe.child_relationships {
if let Some(rel_name) = &rel.relationship_name {
let _ = writeln!(
context,
" - {} (Child: {}.{})",
rel_name, rel.child_sobject, rel.field
);
}
}
}
context
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::Must;
use serde_json::json;
fn mock_describe() -> SObjectDescribe {
let json = json!({
"name": "CustomObj__c",
"label": "Custom Object",
"custom": true,
"queryable": true,
"activateable": false, "createable": true, "customSetting": false, "deletable": true,
"deprecatedAndHidden": false, "feedEnabled": true, "hasSubtypes": false,
"isSubtype": false, "keyPrefix": "a00", "labelPlural": "Custom Objects", "layoutable": true,
"mergeable": true, "mruEnabled": true, "replicateable": true, "retrieveable": true,
"searchable": true, "triggerable": true, "undeletable": true, "updateable": true,
"urls": {}, "recordTypeInfos": [],
"childRelationships": [
{
"cascadeDelete": false,
"childSObject": "ChildObj__c",
"deprecatedAndHidden": false,
"field": "ParentId__c",
"relationshipName": "Children__r",
"restrictedDelete": false
}
],
"fields": [
{
"name": "Id", "type": "id", "label": "Record ID", "custom": false, "nillable": false,
"unique": false, "externalId": false, "calculated": false, "referenceTo": [],
"aggregatable": true, "autoNumber": false, "byteLength": 18, "cascadeDelete": false,
"caseSensitive": false, "createable": false, "defaultedOnCreate": true, "dependentPicklist": false,
"deprecatedAndHidden": false, "digits": 0, "displayLocationInDecimal": false, "encrypted": false,
"filterable": true, "groupable": true, "highScaleNumber": false, "htmlFormatted": false,
"idLookup": true, "length": 18, "nameField": false, "namePointing": false, "permissionable": false,
"polymorphicForeignKey": false, "precision": 0, "queryByDistance": false, "restrictedDelete": false,
"restrictedPicklist": false, "scale": 0, "soapType": "tns:ID", "sortable": true, "updateable": false,
"writeRequiresMasterRead": false
},
{
"name": "Name", "type": "string", "label": "Name", "custom": false, "nillable": false,
"unique": false, "externalId": false, "calculated": false, "referenceTo": [],
"aggregatable": true, "autoNumber": false, "byteLength": 80, "cascadeDelete": false,
"caseSensitive": false, "createable": true, "defaultedOnCreate": false, "dependentPicklist": false,
"deprecatedAndHidden": false, "digits": 0, "displayLocationInDecimal": false, "encrypted": false,
"filterable": true, "groupable": true, "highScaleNumber": false, "htmlFormatted": false,
"idLookup": false, "length": 80, "nameField": true, "namePointing": false, "permissionable": false,
"polymorphicForeignKey": false, "precision": 0, "queryByDistance": false, "restrictedDelete": false,
"restrictedPicklist": false, "scale": 0, "soapType": "xsd:string", "sortable": true, "updateable": true,
"writeRequiresMasterRead": false
},
{
"name": "Account__c", "type": "reference", "label": "Account", "custom": true, "nillable": true,
"unique": false, "externalId": false, "calculated": false, "referenceTo": ["Account"],
"aggregatable": true, "autoNumber": false, "byteLength": 18, "cascadeDelete": false,
"caseSensitive": false, "createable": true, "defaultedOnCreate": false, "dependentPicklist": false,
"deprecatedAndHidden": false, "digits": 0, "displayLocationInDecimal": false, "encrypted": false,
"filterable": true, "groupable": true, "highScaleNumber": false, "htmlFormatted": false,
"idLookup": false, "length": 18, "nameField": false, "namePointing": false, "permissionable": false,
"polymorphicForeignKey": false, "precision": 0, "queryByDistance": false, "restrictedDelete": false,
"restrictedPicklist": false, "scale": 0, "soapType": "tns:ID", "sortable": true, "updateable": true,
"writeRequiresMasterRead": false
},
{
"name": "Amount__c", "type": "currency", "label": "Amount", "custom": true, "nillable": true,
"unique": false, "externalId": false, "calculated": false, "referenceTo": [],
"aggregatable": true, "autoNumber": false, "byteLength": 0, "cascadeDelete": false,
"caseSensitive": false, "createable": true, "defaultedOnCreate": false, "dependentPicklist": false,
"deprecatedAndHidden": false, "digits": 18, "displayLocationInDecimal": false, "encrypted": false,
"filterable": true, "groupable": true, "highScaleNumber": false, "htmlFormatted": false,
"idLookup": false, "length": 0, "nameField": false, "namePointing": false, "permissionable": false,
"polymorphicForeignKey": false, "precision": 18, "queryByDistance": false, "restrictedDelete": false,
"restrictedPicklist": false, "scale": 2, "soapType": "xsd:double", "sortable": true, "updateable": true,
"writeRequiresMasterRead": false
},
{
"name": "ExtId__c", "type": "string", "label": "External ID", "custom": true, "nillable": false,
"unique": true, "externalId": true, "calculated": false, "referenceTo": [],
"aggregatable": true, "autoNumber": false, "byteLength": 255, "cascadeDelete": false,
"caseSensitive": false, "createable": true, "defaultedOnCreate": false, "dependentPicklist": false,
"deprecatedAndHidden": false, "digits": 0, "displayLocationInDecimal": false, "encrypted": false,
"filterable": true, "groupable": true, "highScaleNumber": false, "htmlFormatted": false,
"idLookup": true, "length": 255, "nameField": false, "namePointing": false, "permissionable": false,
"polymorphicForeignKey": false, "precision": 0, "queryByDistance": false, "restrictedDelete": false,
"restrictedPicklist": false, "scale": 0, "soapType": "xsd:string", "sortable": true, "updateable": true,
"writeRequiresMasterRead": false
}
]
});
serde_json::from_value(json).must()
}
#[test]
fn test_generate_llm_context_default() {
let describe = mock_describe();
let options = LlmContextOptions::default();
let context = generate_llm_context(&describe, &options);
assert!(context.contains("SObject: CustomObj__c (Custom: true, Label: Custom Object)"));
assert!(context.contains("- Id: id [req] // Record ID"));
assert!(context.contains("- Name: string [req] // Name"));
assert!(context.contains("- Account__c: reference -> Account // Account"));
assert!(context.contains("- Amount__c: currency // Amount"));
assert!(context.contains("- ExtId__c: string [req,uniq,ext_id] // External ID"));
assert!(context.contains("Child Relationships:"));
assert!(context.contains("- Children__r (Child: ChildObj__c.ParentId__c)"));
}
#[test]
fn test_generate_llm_context_custom_only_no_labels() {
let describe = mock_describe();
let options = LlmContextOptions {
include_labels: false,
include_relationships: false,
custom_fields_only: true,
};
let context = generate_llm_context(&describe, &options);
assert!(!context.contains("- Id:"));
assert!(!context.contains("- Name:"));
assert!(context.contains("- Account__c: reference\n"));
assert!(context.contains("- Amount__c: currency\n"));
assert!(context.contains("- ExtId__c: string [req,uniq,ext_id]\n"));
assert!(!context.contains("// Amount"));
assert!(!context.contains("Child Relationships:"));
assert!(!context.contains("-> Account"));
}
}