{"id":31786779,"url":"https://github.com/nadeesha/structlm","last_synced_at":"2025-10-10T13:25:13.469Z","repository":{"id":305152166,"uuid":"1022056326","full_name":"nadeesha/structlm","owner":"nadeesha","description":"Token-efficient schema definition for getting structured output from LLMs.","archived":false,"fork":false,"pushed_at":"2025-08-03T21:35:11.000Z","size":248,"stargazers_count":6,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-19T00:47:16.575Z","etag":null,"topics":["agents","data-validation","llm","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nadeesha.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-18T11:31:45.000Z","updated_at":"2025-08-03T21:35:15.000Z","dependencies_parsed_at":"2025-07-18T16:51:58.853Z","dependency_job_id":"75364d20-7499-4822-94fa-79995ec0ff9b","html_url":"https://github.com/nadeesha/structlm","commit_stats":null,"previous_names":["nadeesha/structlm"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/nadeesha/structlm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadeesha%2Fstructlm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadeesha%2Fstructlm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadeesha%2Fstructlm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadeesha%2Fstructlm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nadeesha","download_url":"https://codeload.github.com/nadeesha/structlm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadeesha%2Fstructlm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279004077,"owners_count":26083666,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","data-validation","llm","typescript"],"created_at":"2025-10-10T13:25:09.773Z","updated_at":"2025-10-10T13:25:13.464Z","avatar_url":"https://github.com/nadeesha.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# StructLM\n\n[![CI](https://img.shields.io/github/actions/workflow/status/nadeesha/structlm/ci.yml?branch=main\u0026label=CI)](https://github.com/nadeesha/structlm/actions/workflows/ci.yml)\n[![npm version](https://img.shields.io/npm/v/structlm)](https://www.npmjs.com/package/structlm)\n[![npm downloads](https://img.shields.io/npm/dm/structlm)](https://www.npmjs.com/package/structlm)\n[![License](https://img.shields.io/npm/l/structlm)](https://github.com/nadeesha/structlm/blob/main/LICENSE)\n[![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?logo=typescript\u0026logoColor=white)](https://www.typescriptlang.org/)\n\n**Token-efficient schema definition for getting structured output from LLMs.**\n\n## Why StructLM?\n\n- **Compact schema definition**: StructLM uses a proprietary object notation that is more compact and is more token-efficient than JSON schemas.\n\n- **Clear and readable**: StructLM's schema definition is human-readable, and is more similar to natural TypeScript syntax. See [SPECIFICATION.md](SPECIFICATION.md) for the full specification.\n\n- **More expressive validation**: Validations are defined as functions, and are serialized to be sent to LLMs.\n\n- **No accuracy loss**: Despite being more compact, StructLM does not lose any accuracy when generating structured output, when compared to JSON schemas. See [BENCHMARKS.md](BENCHMARKS.md) for more details on our benchmarks.\n\n- **Lightweight**: Zero dependencies, focused solely on runtime schema definition, and output validation.\n\n- **Type-safety**: StructLM provides full zod-like TypeScript type inference at compile time, and assertions at run time.\n\n# Benchmarks\n\nThis is a benchmark of StructLM vs JSON Schema, using Claude 3.5 Haiku. For the full benchmark, see [BENCHMARKS.md](BENCHMARKS.md).\n\n![StructLM vs JSON Schema](./assets/haiku-bench.png)\n\n### Simple Object\n- JSON-Schema: 414 tokens (average)\n- StructLM: 222 tokens (average)\n- Reduction: 46.4% (average)\n- Accuracy: Equal\n\n### Complex Object\n- JSON-Schema: 1460 tokens (average)\n- StructLM: 610 tokens (average)\n- Reduction: 58.2% (average)\n- Accuracy: StructLM is slightly better (+0.4% on average)\n\n### Schema with custom validations\n- JSON-Schema: 852 tokens (average)\n- StructLM: 480 tokens (average)\n- Reduction: 43.7% (average)\n- Accuracy: Equal\n\n## Installation\n\n```bash\nnpm install structlm\n```\n\n## Quick Start\n\n```typescript\nimport { s } from 'structlm';\n\n// Define a user schema\nconst userSchema = s.object({\n  name: s.object({\n    first: s.string(),\n    last: s.string()\n  }),\n  age: s.number(),\n  active: s.boolean(),\n  tags: s.array(s.string())\n});\n\n// Generate schema description for LLM\nconsole.log(userSchema.stringify());\n// Output: \"{ name: { first: string, last: string }, age: number, active: boolean, tags: [string] }\"\n\n// Parse and validate JSON data\nconst userData = userSchema.parse('{\"name\":{\"first\":\"John\",\"last\":\"Doe\"},\"age\":30,\"active\":true,\"tags\":[\"developer\",\"typescript\"]}');\n// Returns: { name: { first: \"John\", last: \"Doe\" }, age: 30, active: true, tags: [\"developer\", \"typescript\"] }\n```\n\n## Simple LLM Integration\n\nHere's a complete example showing how to use StructLM with an LLM to extract structured data:\n\n```typescript\nimport { s } from 'structlm';\n\n// 1. Define your schema\nconst contactSchema = s.object({\n  name: s.string(),\n  email: s.string().validate(email =\u003e email.includes('@')),\n  phone: s.string().optional(),\n  company: s.string()\n});\n\n// 2. Create your prompt with the schema\nconst text = \"Contact John Doe at john@example.com or call (555) 123-4567. He works at Tech Corp.\";\n\nconst prompt = `\nExtract contact information from the following text and return it as JSON matching this structure:\n${contactSchema.stringify()}\n\nText: \"${text}\"\n\nReturn only the JSON object, no additional text.`;\n\n// The schema.stringify() outputs: \n// { name: string, email: string /* email=\u003eemail.includes('@') */, phone: string /* optional */, company: string }\n\n// 3. Send prompt to LLM (the LLM returns this JSON string)\nconst llmResponse = `{\n  \"name\": \"John Doe\",\n  \"email\": \"john@example.com\", \n  \"phone\": \"(555) 123-4567\",\n  \"company\": \"Tech Corp\"\n}`;\n\n// 4. Parse and validate the LLM response\nconst contact = contactSchema.parse(llmResponse);\n// Returns: { name: \"John Doe\", email: \"john@example.com\", phone: \"(555) 123-4567\", company: \"Tech Corp\" }\n\n// The parse() method validates the email format and ensures all required fields are present\n```\n\n## API Reference\n\nFor the specification of the custom object notation, see [SPECIFICATION.md](SPECIFICATION.md).\n\n### Basic Types\n\n#### `s.string()`\nCreates a string schema.\n\n```typescript\nconst nameSchema = s.string();\nconsole.log(nameSchema.stringify()); // \"string\"\n\n// Parse and validate a string\nconst name = nameSchema.parse('\"John\"'); // \"John\"\n```\n\n#### `s.number()`\nCreates a number schema.\n\n```typescript\nconst ageSchema = s.number();\nconsole.log(ageSchema.stringify()); // \"number\"\n\n// Parse and validate a number\nconst age = ageSchema.parse('25'); // 25\n```\n\n#### `s.boolean()`\nCreates a boolean schema.\n\n```typescript\nconst activeSchema = s.boolean();\nconsole.log(activeSchema.stringify()); // \"boolean\"\n\n// Parse and validate a boolean\nconst isActive = activeSchema.parse('true'); // true\n```\n\n### Complex Types\n\n#### `s.array(itemSchema)`\nCreates an array schema with specified item type.\n\n```typescript\nconst numbersSchema = s.array(s.number());\nconsole.log(numbersSchema.stringify()); // \"[number]\"\n\n// Parse and validate an array\nconst numbers = numbersSchema.parse('[1, 2, 3, 4]'); // [1, 2, 3, 4]\n\nconst usersSchema = s.array(s.object({\n  name: s.string(),\n  age: s.number()\n}));\nconsole.log(usersSchema.stringify()); \n// \"[ { name: string, age: number } ]\"\n\n// Parse complex array\nconst users = usersSchema.parse('[{\"name\":\"John\",\"age\":30},{\"name\":\"Jane\",\"age\":25}]');\n// Returns: [{ name: \"John\", age: 30 }, { name: \"Jane\", age: 25 }]\n```\n\n#### `s.object(shape)`\nCreates an object schema with specified properties.\n\n```typescript\nconst personSchema = s.object({\n  name: s.string(),\n  age: s.number(),\n  address: s.object({\n    street: s.string(),\n    city: s.string(),\n    zipCode: s.string()\n  })\n});\n\nconsole.log(personSchema.stringify());\n// \"{ name: string, age: number, address: { street: string, city: string, zipCode: string } }\"\n\n// Parse and validate an object\nconst person = personSchema.parse(`{\n  \"name\": \"John Doe\",\n  \"age\": 30,\n  \"address\": {\n    \"street\": \"123 Main St\",\n    \"city\": \"Anytown\",\n    \"zipCode\": \"12345\"\n  }\n}`);\n// Returns typed object with validation\n```\n\n### Validation\n\n#### `.validate(fn)`\nAdds custom validation using a JavaScript function. \n\n** IMPORTANT **: Validation functions need to be pure functions, and not reference any external variables. \n\n```typescript\nconst emailSchema = s.string().validate(email =\u003e email.includes('@'));\nconst positiveNumberSchema = s.number().validate(n =\u003e n \u003e 0);\nconst adultAgeSchema = s.number().validate(age =\u003e age \u003e= 18);\n\n// Chaining validation with schema definition\nconst userSchema = s.object({\n  email: s.string().validate(email =\u003e email.includes('@')),\n  age: s.number().validate(age =\u003e age \u003e= 0),\n  username: s.string().validate(name =\u003e name.length \u003e= 3)\n});\n```\n\n#### `.optional()`\nMakes a field optional in object schemas.\n\n```typescript\nconst userSchema = s.object({\n  name: s.string(),\n  age: s.number().optional(),\n  bio: s.string().validate(bio =\u003e bio.length \u003c= 500).optional(),\n  tags: s.array(s.string()).optional()\n});\n\nconsole.log(userSchema.stringify());\n// Output: \"{ name: string, age: number /* optional */, bio: string /* bio=\u003ebio.length\u003c=500, optional */, tags: [string] /* optional */ }\"\n\n// All these are valid:\nuserSchema.parse('{\"name\":\"John\"}');\nuserSchema.parse('{\"name\":\"John\",\"age\":30}');\nuserSchema.parse('{\"name\":\"John\",\"age\":30,\"bio\":\"Developer\",\"tags\":[\"js\",\"ts\"]}');\n```\n\n## Type Inference\n\nStructLM provides full TypeScript type inference:\n\n```typescript\nimport { s, Infer } from 'structlm';\n\nconst userSchema = s.object({\n  name: s.string(),\n  age: s.number(),\n  active: s.boolean()\n});\n\ntype User = Infer\u003ctypeof userSchema\u003e;\n// User = { name: string; age: number; active: boolean; }\n```\n\n## Advanced Examples\n\n### Nested Complex Schema\n\n```typescript\nconst apiResponseSchema = s.object({\n  status: s.string().validate(s =\u003e ['success', 'error'].includes(s)),\n  data: s.object({\n    users: s.array(s.object({\n      id: s.number(),\n      profile: s.object({\n        name: s.object({\n          first: s.string(),\n          last: s.string()\n        }),\n        contact: s.object({\n          email: s.string().validate(email =\u003e email.includes('@')),\n          phone: s.string()\n        })\n      }),\n      permissions: s.array(s.string()),\n      metadata: s.object({\n        createdAt: s.string(),\n        lastLogin: s.string(),\n        loginCount: s.number().validate(n =\u003e n \u003e= 0)\n      })\n    }))\n  }),\n  pagination: s.object({\n    page: s.number().validate(n =\u003e n \u003e 0),\n    limit: s.number().validate(n =\u003e n \u003e 0),\n    total: s.number().validate(n =\u003e n \u003e= 0)\n  })\n});\n\nconsole.log(apiResponseSchema.stringify());\n// Outputs clean, readable schema description\n```\n\n### Validation Examples\n\n```typescript\n// Email validation\nconst emailSchema = s.string().validate(email =\u003e {\n  const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n  return emailRegex.test(email);\n});\n\n// Age validation\nconst ageSchema = s.number().validate(age =\u003e age \u003e= 0 \u0026\u0026 age \u003c= 120);\n\n// Username validation\nconst usernameSchema = s.string().validate(username =\u003e {\n  return username.length \u003e= 3 \u0026\u0026 \n         username.length \u003c= 20 \u0026\u0026 \n         /^[a-zA-Z0-9_]+$/.test(username);\n});\n\n// Complex object with multiple validations\nconst registrationSchema = s.object({\n  username: usernameSchema,\n  email: emailSchema,\n  age: ageSchema,\n  password: s.string().validate(pwd =\u003e pwd.length \u003e= 8),\n  confirmPassword: s.string(),\n  acceptTerms: s.boolean().validate(accepted =\u003e accepted === true)\n});\n```\n\n## StructLM vs JSON Schema\n\nStructLM provides a more compact alternative to JSON Schema for LLM applications. Here's how they compare:\n\n**JSON Schema:**\n```json\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"minLength\": 2\n    },\n    \"email\": {\n      \"type\": \"string\",\n      \"format\": \"email\"\n    },\n    \"age\": {\n      \"type\": \"number\",\n      \"minimum\": 18,\n      \"maximum\": 120\n    },\n    \"roles\": {\n      \"type\": \"array\",\n      \"items\": { \"type\": \"string\" },\n      \"minItems\": 1\n    }\n  },\n  \"required\": [\"name\", \"email\", \"age\", \"roles\"]\n}\n```\n\n**StructLM Schema:**\n```text\n{ \n  name: string /* name=\u003ename.length\u003e=2 */, \n  email: string /* email=\u003eemail.includes(\"@\") */, \n  age: number /* age=\u003eage\u003e=18\u0026\u0026age\u003c=120 */, \n  roles: [string] /* arr=\u003earr.length\u003e=1 */ \n}\n```\n\nTo get this schema, this is the expression you would use:\n\n```typescript\nconst userSchema = s.object({\n  name: s.string().validate(name =\u003e name.length \u003e= 2),\n  email: s.string().validate(email =\u003e email.includes('@')),\n  age: s.number().validate(age =\u003e age \u003e= 18 \u0026\u0026 age \u003c= 120),\n  roles: s.array(s.string()).validate(arr =\u003e arr.length \u003e= 1)\n});\n```\n\n## Frequently Asked Questions\n\n### **General Usage**\n\n#### **Q: How is StructLM different from Zod?**\nA: While StructLM is inspired by Zod's API, it's specifically designed for LLM integration. StructLM generates compact schema descriptions optimized for AI prompts (XX% fewer tokens), while Zod focuses on general TypeScript validation. StructLM's `.stringify()` method produces LLM-friendly output, whereas Zod employs `zod-to-json-schema` or equivalent tools.\n\n#### **Q: Can I use StructLM for regular data validation without LLMs?**\nA: Yes! StructLM mostly works for standard TypeScript data validation. Use `.parse()` for validation and type inference just like Zod. However, StructLM's main advantage is its token-efficient LLM integration capabilities. Therefore, some of the more advanced Typescript features like discriminated unions, recursive types, etc. may not work as expected right now.\n\n#### **Q: Which LLMs work with StructLM?**\nStructLM itself is model agnostic, and works as a schema definition and data validation library. Reliability may vary by model, but our benchmarks show consistent results across major providers.\n\n#### **Q: Does StructLM work in the browser?**\nA: Yes! StructLM is a lightweight TypeScript library with zero dependencies that works in browsers, Node.js, Deno, and Bun.\n\n### **Schema Definition**\n\n#### **Q: How do I make a field optional?**\nA: Use the `.optional()` method on any field:\n```typescript\nconst schema = s.object({\n  name: s.string(),\n  age: s.number().optional(),\n  email: s.string().validate(e =\u003e e.includes('@')).optional()\n});\n\n// Outputs: { name: string, age: number /* optional */, email: string /* e=\u003ee.includes(\"@\"), optional */ }\n```\n\nOptional fields are excluded from validation when missing from the input data.\n\n#### **Q: Can I use unions/discriminated unions like in Zod?**\nA: Union types are not currently supported but are on the roadmap. For now, use string validation with enums:\n```typescript\nconst statusSchema = s.string().validate(status =\u003e \n  ['pending', 'approved', 'rejected'].includes(status)\n);\n```\n\n#### **Q: How do I validate nested arrays?**\nA: Use nested `s.array()` calls:\n```typescript\nconst matrixSchema = s.array(s.array(s.number()));\n// Outputs: [[number]]\n\nconst complexSchema = s.array(\n  s.object({\n    items: s.array(s.string()).validate(arr =\u003e arr.length \u003e 0)\n  })\n);\n```\n\n#### **Q: Can validation functions access other fields in the object?**\nA: No, validation functions only receive the current field's value. Cross-field validation isn't currently supported.\n\n### **LLM Integration**\n\n#### **Q: Do LLMs really understand StructLM's compact format better?**\nA: Our benchmarks show equal or better accuracy compared to JSON Schema. The compact format is:\n- Less verbose and confusing\n- More similar to natural TypeScript syntax\n- Includes validation hints inline\n- Reduces prompt complexity\n\n#### **Q: Can I combine multiple schemas in one prompt?**\nA: Yes! Use `.stringify()` on multiple schemas:\n```typescript\nconst userSchema = s.object({...});\nconst orderSchema = s.object({...});\n\nconst prompt = `\nProcess this data and return:\n- User: ${userSchema.stringify()}\n- Order: ${orderSchema.stringify()}\n`;\n```\n\n#### **Q: How do I handle LLM responses that don't match the schema?**\nA: StructLM's `.parse()` method throws descriptive errors for invalid data:\n```typescript\ntry {\n  const result = schema.parse(llmResponse);\n} catch (error) {\n  console.log('LLM returned invalid data:', error.message);\n  // Handle error: retry, use fallback, etc.\n}\n```\n\n### **Performance**\n\n#### **Q: What's the performance overhead?**\nA: StructLM is lightweight:\n- Schema creation: Minimal overhead\n- `.stringify()`: Fast string concatenation\n- `.parse()`: JSON.parse + validation functions\n- No runtime dependencies\n\n#### **Q: Can I pre-compile schemas for better performance?**\nA: Schema stringification is already very fast, but you can cache results:\n```typescript\nconst userSchemaString = userSchema.stringify();\n// Reuse userSchemaString in multiple prompts\n```\n\n### **Troubleshooting**\n\n#### **Q: Why is my validation function not working in LLM prompts?**\nA: Validation functions are serialized as text hints for LLMs but only enforced during `.parse()`. Make sure your function:\n- Uses simple, clear logic\n- Doesn't reference external variables\n- Is readable when converted to string\n\n#### **Q: Can I see what the validation hints look like?**\nA: Yes! Use `.stringify()` to see exactly what gets sent to the LLM:\n```typescript\nconsole.log(schema.stringify());\n// Shows the compact format with validation hints\n```\n\n## Contributing\n\nWe welcome contributions! Please open an issue or submit a pull request on GitHub.\n\n## License\n\nApache 2.0 License\n\n## Support\n\n- 🐛 [Report Issues](https://github.com/nadeesha/structlm/issues)\n- 💡 [Feature Requests](https://github.com/nadeesha/structlm/discussions)\n- 📖 [Documentation](https://github.com/nadeesha/structlm)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnadeesha%2Fstructlm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnadeesha%2Fstructlm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnadeesha%2Fstructlm/lists"}