# LLM Integration
Using `generateText` and `generateObject` in patterns.
**Important:** These functions can only be called from pattern bodies, not handlers or `computed()`.
---
## generateText
Free-form text generation.
```typescript
const response = generateText({
prompt: userInput,
system: "You are a helpful assistant.", // optional
});
// Response: { result: string, error: string, pending: boolean }
{ifElse(response.pending,
Generating...,
ifElse(response.error,
Error: {response.error},
{response.result}
)
)}
```
---
## generateObject\
Structured data matching a TypeScript type. Schema is inferred automatically.
```typescript
interface ProductIdea {
name: string;
description: string;
price: number;
}
const idea = generateObject({
prompt: userInput,
system: "Generate a product idea.",
model: "anthropic:claude-sonnet-4-5",
});
// Response: { result: ProductIdea, error: string, pending: boolean }
{ifElse(idea.pending,
Generating...,
ifElse(idea.error,
Error: {idea.error},
{idea.result?.name}
{idea.result?.description}
${idea.result?.price}
)
)}
```
### Schema Root Must Be Object
Arrays at root fail (OpenAI API requirement). Wrap in object:
```typescript
// ❌ Fails
generateObject({...})
// ✅ Works
interface TodoResponse { todos: TodoItem[]; }
generateObject({...})
// Access via result.todos
```
### Requires `/// `
If you get "schema is required" error, add to file top:
```typescript
///
```
---
## Handling Pending State
### With ct-loader
```typescript
{ifElse(
data.pending,
Generating...,
{data.result}
)}
```
### With Elapsed Time and Cancel
```typescript
const { result, pending, error, cancel } = generateText({ prompt });
{ifElse(
pending,
Generating...,
{result}
)}
```
### Disable Button While Pending
```typescript
{ifElse(analysis.pending, "Analyzing...", "Regenerate")}
```
---
## Processing Arrays
Map over items - caching is automatic per-item:
```typescript
const summaries = articles.map((article) => ({
article,
summary: generateText({
prompt: computed(() => `Summarize: ${article.title}\n${article.content}`),
}),
}));
{summaries.map(({ article, summary }) => (
{article.title}
{ifElse(summary.pending,
Summarizing...,
{summary.result}
)}
))}
```
### Template Strings Require computed()
When referencing multiple properties from reactive references:
```typescript
// ❌ Fails: reactive references can't be used in template strings directly
prompt: `Title: ${article.title}\nContent: ${article.content}`
// ✅ Works: defer evaluation with computed()
prompt: computed(() => `Title: ${article.title}\nContent: ${article.content}`)
```
---
## Constraints
### Call From Pattern Body Only
```typescript
// ❌ Won't work - handler
const myHandler = handler((_, { prompt }) => {
const result = generateText({ prompt });
});
// ❌ Won't work - computed()
const summary = computed(() => generateText({ prompt }));
// ✅ Works - pattern body
export default pattern(({ prompt }) => {
const result = generateText({ prompt });
return { [UI]: {result.result}
};
});
```
### No Async/Await
Results are reactive nodes, not promises:
```typescript
// ❌ Don't await
const result = await generateText({ prompt });
// ✅ Check pending/error/result
const result = generateText({ prompt });
if (!result.pending && !result.error) {
// use result.result
}
```
---
## Automatic Caching
The framework caches LLM responses by content hash. Don't build custom caching:
```typescript
// ❌ Unnecessary - framework handles this
const cache = new Map();
if (!cache.has(key)) cache.set(key, generateText({...}));
// ✅ Just call directly - caching is automatic
const result = generateText({ prompt });
```
When adding items to an array, only new items trigger LLM requests.
---
## Summary
| Function | Returns | Use For |
|----------|---------|---------|
| `generateText` | `{ result: string, error, pending }` | Free-form text |
| `generateObject` | `{ result: T, error, pending }` | Structured data |
**Key rules:**
- Call from pattern body only
- Handle pending/error/result states
- Use `computed()` for template strings with reactive references
- Schema root must be object (not array)