Compare commits

..

8 Commits

Author SHA1 Message Date
Clelia (Astra) Bertelli 2579cb6b96 chore: move to zod/v4 2025-07-08 21:38:35 +02:00
Clelia (Astra) Bertelli 28fb8247f8 ci: patching anthropic with changesets 2025-07-08 19:07:28 +02:00
Clelia (Astra) Bertelli 82e69bd5c4 feat: adding structured output support for anthropic 2025-07-08 16:51:13 +02:00
Logan 8eeac3310f fix memory factory (#2066) 2025-07-08 10:01:19 +07:00
Logan 984a573068 docs: update contributing instructions (#2067) 2025-07-07 16:38:26 -07:00
github-actions[bot] f0160d9646 Release (#2065)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-07 12:15:33 -06:00
Logan 39758ab018 add title to root layout (#2064) 2025-07-07 12:06:13 -06:00
dependabot[bot] f631d4f7d6 chore(deps): bump next from 15.3.0 to 15.3.3 (#2063) 2025-07-07 12:40:42 +07:00
17 changed files with 675 additions and 369 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@llamaindex/anthropic": patch
---
Adding structured output support
+5
View File
@@ -0,0 +1,5 @@
---
"@llamaindex/core": patch
---
Fix createMemory factory when parsing options
+51
View File
@@ -38,6 +38,7 @@ npm install -g pnpm
```shell
pnpm install
pnpm install -g tsx
```
### Build the packages
@@ -48,6 +49,56 @@ To build all packages, run:
pnpm build
```
### Start Developing
You can launch the package in dev-mode by running:
```shell
pnpm dev
```
This will use turbo to run all packages in watch-mode. This means you can make changes and have them automatically built.
If you want to customize what packages are built/watched, you can run turbo directly and adjust the filter:
```shell
pnpm turbo run dev --filter="./packages/core" --concurrency=100
```
In another terminal, you can write and run any script needed to quickly test your changes. For example:
```typescript
import { createMemory, staticBlock } from "@llamaindex/core/memory";
// Create memory with predefined context
const memory = createMemory({
memoryBlocks: [
staticBlock({
content:
"The user is a software engineer who loves TypeScript and LlamaIndex.",
messageRole: "system",
}),
],
});
async function main() {
const result = await memory.getLLM();
console.log(result);
}
void main().catch(console.error);
```
And run it with:
```shell
pnpm exec tsx my_script.ts
```
This flow allows you to easily test your changes without having to build the entire project.
Once you are happy with your changes, be sure to add tests (and confirm existing tests are passing!).
### Run tests
#### Unit tests
+6
View File
@@ -1,5 +1,11 @@
# @llamaindex/doc
## 0.2.34
### Patch Changes
- 39758ab: Add title to homepage header
## 0.2.33
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@llamaindex/doc",
"version": "0.2.33",
"version": "0.2.34",
"private": true,
"scripts": {
"postinstall": "fumadocs-mdx",
@@ -50,7 +50,7 @@
"hast-util-to-jsx-runtime": "^2.3.2",
"llamaindex": "workspace:*",
"lucide-react": "^0.460.0",
"next": "^15.3.0",
"next": "^15.3.3",
"next-themes": "^0.4.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",
+3
View File
@@ -32,6 +32,9 @@ export default function Layout({ children }: { children: ReactNode }) {
sizes="16x16"
href="/favicon-16x16.png"
/>
<title>
LlamaIndex.TS - Build LLM-powered document agents and workflows
</title>
</head>
<body className="flex min-h-screen flex-col">
<TooltipProvider>
+1 -1
View File
@@ -10,7 +10,7 @@
"dependencies": {
"ai": "^4.0.0",
"llamaindex": "workspace:*",
"next": "^15.3.0",
"next": "^15.3.3",
"react": "19.0.0",
"react-dom": "19.0.0"
},
@@ -9,7 +9,7 @@
},
"dependencies": {
"llamaindex": "workspace:*",
"next": "^15.3.0",
"next": "^15.3.3",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
@@ -11,7 +11,7 @@
"@llamaindex/huggingface": "workspace:*",
"@llamaindex/readers": "workspace:*",
"llamaindex": "workspace:*",
"next": "^15.3.0",
"next": "^15.3.3",
"react": "19.0.0",
"react-dom": "19.0.0"
},
+1 -1
View File
@@ -4,7 +4,7 @@
"scripts": {
"clean": "find . -type d \\( -name .turbo -o -name node_modules -o -name dist -o -name .next -o -name lib \\) -exec rm -rf {} +",
"build": "turbo run build --filter=\"./packages/*\" --filter=\"./packages/providers/**\"",
"dev": "turbo run dev --filter=\"./packages/*\" --filter=\"./packages/providers/**\"",
"dev": "turbo run dev --filter=\"./packages/*\" --filter=\"./packages/providers/**\" --concurrency=100",
"format": "prettier --ignore-unknown --cache --check .",
"format:write": "prettier --ignore-unknown --write .",
"lint": "turbo run lint",
+1 -1
View File
@@ -76,7 +76,7 @@
"@types/json-schema": "^7.0.15",
"@types/node": "^22.9.0",
"llamaindex": "workspace:*",
"next": "^15.3.0",
"next": "^15.3.3",
"rollup": "^4.28.1",
"tsx": "^4.19.3",
"typescript": "^5.7.3",
+12 -1
View File
@@ -79,7 +79,18 @@ export function createMemory<TMessageOptions extends object = object>(
}
}
}
return new Memory<Record<string, never>, TMessageOptions>(messages, options);
// Determine the correct options to pass to Memory
const resolvedOptions: MemoryOptions<TMessageOptions> = Array.isArray(
messagesOrOptions,
)
? options
: (messagesOrOptions as MemoryOptions<TMessageOptions>);
return new Memory<Record<string, never>, TMessageOptions>(
messages,
resolvedOptions,
);
}
/**
+44 -1
View File
@@ -1,6 +1,6 @@
import { Settings } from "@llamaindex/core/global";
import type { ChatMessage, LLM } from "@llamaindex/core/llms";
import { createMemory, Memory } from "@llamaindex/core/memory";
import { createMemory, Memory, staticBlock } from "@llamaindex/core/memory";
import { MockLLM } from "@llamaindex/core/utils";
import type { Tokenizer } from "@llamaindex/env/tokenizers";
import {
@@ -392,4 +392,47 @@ describe("Memory", () => {
expect(messages[0]?.role).toBe("user"); // data role should be mapped to user
});
});
describe("memoryBlocks initialization", () => {
test("should include static block content in getLLM result", async () => {
const STATIC_CONTENT = "You are speaking with a helpful assistant.";
const block = staticBlock({
content: STATIC_CONTENT,
messageRole: "system",
});
const memoryWithBlock = createMemory({ memoryBlocks: [block] });
// Fetch messages via getLLM static block (priority 0) should always be present
const messages = await memoryWithBlock.getLLM();
// There should be exactly one message (the static block) when no other messages are added
expect(messages).toHaveLength(1);
expect(messages[0]?.content).toBe(STATIC_CONTENT);
expect(messages[0]?.role).toBe("system");
});
test("should retain static block alongside dynamic messages", async () => {
const STATIC_CONTENT = "Always respond in pirate speak.";
const block = staticBlock({
content: STATIC_CONTENT,
messageRole: "system",
});
const memoryWithBlock = createMemory({ memoryBlocks: [block] });
// Add a regular user message
await memoryWithBlock.add({ role: "user", content: "Hello there!" });
const messages = await memoryWithBlock.getLLM();
// Static block + user message
expect(messages).toHaveLength(2);
const contents = messages.map((m) => m.content);
expect(contents).toEqual(
expect.arrayContaining([STATIC_CONTENT, "Hello there!"]),
);
});
});
});
+4 -3
View File
@@ -31,9 +31,9 @@
"test": "vitest run"
},
"devDependencies": {
"vitest": "^2.1.5",
"@llamaindex/core": "workspace:*",
"@llamaindex/env": "workspace:*"
"@llamaindex/env": "workspace:*",
"vitest": "^2.1.5"
},
"peerDependencies": {
"@llamaindex/core": "workspace:*",
@@ -41,6 +41,7 @@
},
"dependencies": {
"@anthropic-ai/sdk": "0.37.0",
"remeda": "^2.17.3"
"remeda": "^2.17.3",
"zod": "^3.25.67"
}
}
+63 -12
View File
@@ -30,6 +30,7 @@ import { ToolCallLLM } from "@llamaindex/core/llms";
import { extractText } from "@llamaindex/core/utils";
import { getEnv } from "@llamaindex/env";
import { isDeepEqual } from "remeda";
import * as z from "zod/v4";
export class AnthropicSession {
anthropic: SDKAnthropic;
@@ -203,7 +204,7 @@ export class Anthropic extends ToolCallLLM<
].contextWindow
: 200000,
tokenizer: undefined,
structuredOutput: false,
structuredOutput: true,
};
}
@@ -459,7 +460,7 @@ export class Anthropic extends ToolCallLLM<
| ChatResponse<AnthropicToolCallLLMMessageOptions>
| AsyncIterable<ChatResponseChunk<AnthropicToolCallLLMMessageOptions>>
> {
const { messages, stream, tools } = params;
const { messages, stream, tools, responseFormat } = params;
// Handle system messages
let systemPrompt: string | BetaTextBlockParam[] | null = null;
@@ -511,10 +512,42 @@ export class Anthropic extends ToolCallLLM<
),
};
let jsonTool: undefined | Tool;
if (responseFormat && this.metadata.structuredOutput) {
if (responseFormat instanceof z.ZodType) {
const schemaDefinition = z.toJSONSchema(responseFormat);
if (!schemaDefinition) {
console.error(responseFormat);
throw new Error(
"Failed to generate JSON schema for provided schema.",
);
}
jsonTool = {
name: "json",
description: "Respond with a JSON object.",
input_schema: schemaDefinition as Tool.InputSchema,
};
}
}
if (tools?.length) {
const apiTools = this.prepareToolsForAPI(tools);
if (jsonTool) {
apiTools.push(jsonTool);
}
Object.assign(apiParams, {
tools: this.prepareToolsForAPI(tools),
tools: apiTools,
});
} else {
const formatTools: Tool[] = [];
if (jsonTool) {
formatTools.push(jsonTool);
Object.assign(apiParams, {
tools: formatTools,
tool_choice: { name: "json", type: "tool" },
});
}
}
if (stream) {
@@ -531,6 +564,17 @@ export class Anthropic extends ToolCallLLM<
(content): content is ToolUseBlock => content.type === "tool_use",
);
let jsonResult: undefined | z.ZodSafeParseResult<unknown>;
if (toolUseBlock?.length) {
const jsonToolUse = toolUseBlock.filter(
(toolUse): toolUse is ToolUseBlock => toolUse.name === "json",
)[0];
if (jsonToolUse) {
jsonResult = (responseFormat as z.ZodType).safeParse(jsonToolUse.input);
}
}
const toolCall =
toolUseBlock.length > 0
? {
@@ -545,18 +589,25 @@ export class Anthropic extends ToolCallLLM<
}
: {};
let messageContent: { type: "text"; text: string }[] | object =
response.content
.filter(
(content): content is TextBlock =>
content.type === "text" && content.text?.trim().length > 0,
)
.map((content) => ({
type: "text" as const,
text: content.text,
}));
if (jsonResult) {
messageContent = jsonResult.data as object;
}
return {
raw: response,
message: {
content: response.content
.filter(
(content): content is TextBlock =>
content.type === "text" && content.text?.trim().length > 0,
)
.map((content) => ({
type: "text" as const,
text: content.text,
})),
content: messageContent as { type: "text"; text: string }[],
role: "assistant",
options: {
...toolCall,
+30
View File
@@ -0,0 +1,30 @@
import * as z from "zod/v4";
import { Anthropic } from "./src";
const responseSchema = z.object({
title: z.string(),
author: z.string(),
year: z.number(),
});
const llm = new Anthropic({ model: "claude-4-0-sonnet" });
async function main() {
const response = await llm.chat({
messages: [
{
role: "system",
content: `You are a book expert. Your task is, given a user message, extract the title, author and publication year of the book and output them in JSON format.`,
},
{
role: "user",
content: `I have been reading La Divina Commedia by Dante Alighieri, published in 1321, which tells the story of a guy who goes through Hell, Purgatory and Heaven just to meet his beloved ex-girlfriend.`,
},
],
responseFormat: responseSchema,
});
return response.message.content;
}
const antResponse = await main();
console.log(antResponse);
+445 -345
View File
File diff suppressed because it is too large Load Diff