01 Τι φέρνει το SDK 7
Το AI SDK 7 κυκλοφόρησε τον Ιούνιο του 2026 και, σύμφωνα με τη Vercel, δεν είναι απλώς ένα wrapper για κλήσεις μοντέλων. Στοχεύει σε agents που τρέχουν σε production, με υποστήριξη για text, audio, realtime, εικόνα και video στους μεγάλους providers.
WorkflowAgent
Durable execution για agents μεγάλης διάρκειας, μέσω του @ai-sdk/workflow.
Νέο telemetry
Ξεχωριστό πακέτο @ai-sdk/otel με OpenTelemetry GenAI spans, callbacks και στατιστικά ανά step.
HarnessAgent
Ίδιο API για Claude Code, Codex, Pi. Αλλάζεις harness όπως αλλάζεις μοντέλο.
Reasoning & tools
Top-level reasoning, typed tool context, tool approvals και λεπτομερή timeouts.
Terminal UI
Το @ai-sdk/tui σου επιτρέπει να δοκιμάσεις έναν agent στο τερματικό πριν γράψεις UI.
Uploads
Provider-independent αρχεία, ώστε το ίδιο attachment να δουλεύει σε διαφορετικά μοντέλα.
import ή αρχεία .mjs). Αν το project σου χρησιμοποιεί require(), θα χρειαστεί μετατροπή.02 Αρχιτεκτονική του chatbot
Το chatbot που θα φτιάξουμε έχει τρία κομμάτια: ένα UI που στέλνει μηνύματα, ένα route που μιλάει με το μοντέλο και ένα stream που γυρίζει πίσω token-token.
03 Εγκατάσταση
- Φτιάξε project:
npx create-next-app@latest my-chat - Πρόσθεσε τα πακέτα:
pnpm add ai @ai-sdk/react zod - Βάλε στο
.env.localτο κλειδί του AI Gateway (AI_GATEWAY_API_KEY) ή τον provider που προτιμάς.
04 Το API route
Το streamText καλεί το μοντέλο και επιστρέφει stream. Το convertToModelMessages μετατρέπει τα μηνύματα του UI στη μορφή που καταλαβαίνει το μοντέλο.
import { streamText, convertToModelMessages, type UIMessage } from 'ai';
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: 'anthropic/claude-sonnet-4.5', // άλλαξέ το με όποιο μοντέλο θέλεις
system: 'Είσαι ένας φιλικός βοηθός. Απαντάς σύντομα και καθαρά.',
messages: await convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}provider/model), οπότε η αλλαγή μοντέλου είναι αλλαγή μίας γραμμής.05 Το UI με useChat
Το useChat κρατά τα μηνύματα, το status και το streaming. Κάθε μήνυμα έχει parts: κείμενο, reasoning, tool calls κ.ά., γι' αυτό τα εμφανίζεις ανά τύπο.
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function Chat() {
const { messages, sendMessage, status } = useChat();
const [input, setInput] = useState('');
return (
<main>
{messages.map(m => (
<div key={m.id}>
<b>{m.role}:</b>{' '}
{m.parts.map((p, i) =>
p.type === 'text' ? <span key={i}>{p.text}</span> : null)}
</div>
))}
<form onSubmit={e => {
e.preventDefault();
if (!input.trim()) return;
sendMessage({ text: input });
setInput('');
}}>
<input value={input} onChange={e => setInput(e.target.value)}
disabled={status !== 'ready'} placeholder="Γράψε κάτι..." />
</form>
</main>
);
}Τρέξε pnpm dev και θα έχεις ήδη ένα chatbot με streaming. Το status (submitted, streaming, ready, error) σου δίνει όλα όσα χρειάζεσαι για loading states και κουμπί «Stop».
06 Πώς δουλεύει το streaming
Όταν στέλνεις μήνυμα, το route δεν περιμένει να ολοκληρωθεί η απάντηση. Το μοντέλο παράγει tokens και το SDK τα προωθεί στον browser μέσω ενός UI message stream, πάνω από HTTP με server-sent events. Ο browser δεν λαμβάνει απλό κείμενο αλλά τυποποιημένα γεγονότα, και αυτό είναι που επιτρέπει στο useChat να χτίζει τα parts χωρίς δικό σου parsing.
Time to first token
Ο χρήστης βλέπει την πρώτη λέξη σε κλάσματα δευτερολέπτου, αντί να περιμένει ολόκληρη την απάντηση. Η αίσθηση ταχύτητας μετράει περισσότερο από τον συνολικό χρόνο.
Ακύρωση
Αν ο χρήστης πατήσει Stop ή κλείσει το tab, το request ακυρώνεται και σταματάς να πληρώνεις tokens που κανείς δεν θα διαβάσει.
Parts αντί για string
Κείμενο, reasoning και tool calls έρχονται ως ξεχωριστά κομμάτια, άρα μπορείς να τα σχεδιάσεις διαφορετικά (π.χ. collapsible reasoning).
07 Καλύτερο UX: Stop, Retry, λάθη και reasoning
Ένα chatbot που δουλεύει στο ευτυχές σενάριο δεν αρκεί. Το useChat επιστρέφει έτοιμα εργαλεία για τα υπόλοιπα:
const { messages, sendMessage, status, stop, regenerate, error } = useChat();
{status === 'streaming' && <button onClick={stop}>Stop</button>}
{error && <button onClick={() => regenerate()}>Ξαναδοκίμασε</button>}
// μέσα στο m.parts.map:
p.type === 'reasoning' ? <details key={i}><summary>Σκέψη</summary>{p.text}</details> : nullΣτον server, μην αφήνεις να διαρρέουν εσωτερικά μηνύματα σφάλματος (URLs, κλειδιά, stack traces) στον χρήστη. Το toUIMessageStreamResponse δέχεται onError, όπου επιστρέφεις ένα ουδέτερο μήνυμα:
return result.toUIMessageStreamResponse({
onError: () => 'Κάτι πήγε στραβά. Δοκίμασε ξανά.',
});08 Αποθήκευση συνομιλιών
Χωρίς persistence, ένα refresh σβήνει τη συζήτηση. Το πρότυπο είναι απλό: κάθε συνομιλία έχει ένα chatId, αποθηκεύεις τα μηνύματα όταν τελειώνει η απάντηση και τα φορτώνεις ως αρχική κατάσταση.
- Δημιούργησε
chatId(π.χ. στο URL/chat/[id]) και πίνακα στη βάση σου (Postgres, Redis, ό,τι χρησιμοποιείς). - Στο route πέρνα τα προηγούμενα μηνύματα και αποθήκευσε το αποτέλεσμα στο
onFinish. - Στον client φόρτωσε τα αποθηκευμένα μηνύματα ως αρχική τιμή.
// server
return result.toUIMessageStreamResponse({
originalMessages: messages,
onFinish: async ({ messages }) => { await saveChat(chatId, messages); },
});
// client
const chat = useChat({ id: chatId, messages: initialMessages });Το saveChat και το initialMessages είναι δικές σου συναρτήσεις. Το SDK δεν σε δεσμεύει σε συγκεκριμένη βάση.
09 Δώσε του tools
Ένα chatbot γίνεται χρήσιμο όταν μπορεί να κάνει πράγματα: να ψάξει, να υπολογίσει, να καλέσει ένα API. Ορίζεις το tool με schema (zod) και συνάρτηση execute.
import { tool, isStepCount } from 'ai';
import { z } from 'zod';
const result = streamText({
model: 'anthropic/claude-sonnet-4.5',
messages: await convertToModelMessages(messages),
stopWhen: isStepCount(5), // μέχρι 5 γύροι tool → απάντηση
tools: {
weather: tool({
description: 'Επιστρέφει τη θερμοκρασία μιας πόλης',
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => ({ city, tempC: 21 }), // εδώ καλείς πραγματικό API
}),
},
});Το μοντέλο αποφασίζει πότε θα καλέσει το tool, το SDK εκτελεί τη συνάρτηση και στέλνει το αποτέλεσμα πίσω στο μοντέλο, που συνεχίζει την απάντηση. Όλο αυτό ονομάζεται tool loop.
Approvals για επικίνδυνες ενέργειες
Στο SDK 7 οι εγκρίσεις (human-in-the-loop) χτίζονται μέσα στο tool layer. Για tools που στέλνουν email, σβήνουν δεδομένα ή χρεώνουν, ζητάς επιβεβαίωση του χρήστη πριν εκτελεστούν. Δες στα docs τη ακριβή σύνταξη της έκδοσής σου.
10 Structured output: απαντήσεις σε JSON, όχι σε κείμενο
Όταν το αποτέλεσμα θα το καταναλώσει κώδικας (φόρμα, βάση, UI κάρτα), δεν θες ελεύθερο κείμενο. Δίνεις ένα zod schema και το SDK επιστρέφει τυποποιημένο αντικείμενο, επικυρωμένο απέναντι στο schema.
import { generateText, Output } from 'ai';
import { z } from 'zod';
const { output } = await generateText({
model: 'anthropic/claude-sonnet-4.5',
output: Output.object({
schema: z.object({
sentiment: z.enum(['θετικό', 'ουδέτερο', 'αρνητικό']),
topics: z.array(z.string()).describe('Έως 3 θέματα'),
urgent: z.boolean(),
}),
}),
prompt: 'Ταξινόμησε: "Το site έπεσε ξανά και χάνουμε πελάτες!"',
});
// output.sentiment, output.topics, output.urgent: πλήρως typedΚαλά schemas
Enums αντί για ελεύθερα strings και .describe() σε κάθε πεδίο. Το μοντέλο διαβάζει τις περιγραφές.
Πού αξίζει
Ταξινόμηση tickets, εξαγωγή στοιχείων από email, παραγωγή quiz, routing αιτημάτων σε agents.
11 Πλήρες tutorial RAG: chatbot που απαντά από τα δικά σου έγγραφα
Το μοντέλο δεν ξέρει τα εσωτερικά σου έγγραφα και μπορεί να «εφεύρει» απαντήσεις. Το RAG (Retrieval-Augmented Generation) το λύνει: πριν απαντήσει, το chatbot ψάχνει στα έγγραφά σου και χρησιμοποιεί μόνο ό,τι βρήκε.
Βήμα 1: ingestion και αναζήτηση
Σπάμε το κείμενο σε κομμάτια με μικρή επικάλυψη, τα μετατρέπουμε σε διανύσματα (embeddings) και τα αποθηκεύουμε. Εδώ χρησιμοποιώ μνήμη, ώστε να τρέχει χωρίς βάση.
import { embed, embedMany, cosineSimilarity } from 'ai';
const embeddingModel = 'openai/text-embedding-3-small';
const store: { text: string; embedding: number[] }[] = [];
export function chunk(text: string, size = 800, overlap = 100) {
const out: string[] = [];
for (let i = 0; i < text.length; i += size - overlap) out.push(text.slice(i, i + size));
return out;
}
export async function ingest(text: string) {
const chunks = chunk(text);
const { embeddings } = await embedMany({ model: embeddingModel, values: chunks });
chunks.forEach((t, i) => store.push({ text: t, embedding: embeddings[i] }));
}
export async function search(query: string, k = 4) {
const { embedding } = await embed({ model: embeddingModel, value: query });
return store
.map(c => ({ text: c.text, score: cosineSimilarity(embedding, c.embedding) }))
.sort((a, b) => b.score - a.score)
.slice(0, k);
}Βήμα 2: η αναζήτηση γίνεται tool
Δίνουμε το search στο μοντέλο ως tool, ώστε να αποφασίζει μόνο του πότε χρειάζεται (π.χ. όχι για ένα «γεια σου»).
import { search } from '@/lib/rag';
const result = streamText({
model: 'anthropic/claude-sonnet-4.5',
system: 'Απάντα ΜΟΝΟ με βάση τα αποτελέσματα του searchDocs. ' +
'Αν δεν βρεις σχετική πληροφορία, πες ότι δεν ξέρεις.',
messages: await convertToModelMessages(messages),
stopWhen: isStepCount(4),
tools: {
searchDocs: tool({
description: 'Ψάχνει στη βάση γνώσης και επιστρέφει σχετικά αποσπάσματα',
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => (await search(query)).map(r => r.text),
}),
},
});Βήμα 3: φόρτωσε έγγραφα
Φτιάξε ένα route (π.χ. /api/ingest) που δέχεται κείμενο και καλεί ingest(text). Για PDF, εξήγαγε πρώτα το κείμενο με κάποια βιβλιοθήκη.
| Παράμετρος | Τι ρυθμίζει | Πρακτική συμβουλή |
|---|---|---|
| Μέγεθος chunk | Πόση πληροφορία έχει κάθε κομμάτι | Πολύ μικρό χάνει νόημα, πολύ μεγάλο θολώνει την αναζήτηση. Ξεκίνα στους 500-1000 χαρακτήρες. |
| Overlap | Επικάλυψη μεταξύ chunks | 10-15%, ώστε να μην κόβονται προτάσεις στη μέση. |
| k (top-k) | Πόσα αποσπάσματα στέλνεις στο μοντέλο | 3-6. Περισσότερα σημαίνει θόρυβο και κόστος. |
store με vector βάση (π.χ. Postgres με pgvector). Το ίδιο μοντέλο embeddings πρέπει να χρησιμοποιείται σε ingestion και αναζήτηση. Πρόσθεσε metadata (πηγή, σελίδα) ώστε το chatbot να παραθέτει από πού πήρε την απάντηση.12 Όταν ο agent χρειάζεται ώρες: WorkflowAgent
Ένα απλό request/response σταματά να αρκεί όταν ο agent περιμένει έγκριση, καλεί αργά APIs ή τρέχει για λεπτά. Το WorkflowAgent προσθέτει durable execution: αν ο server κάνει restart ή το function λήξει, η εκτέλεση συνεχίζει από εκεί που έμεινε αντί να ξεκινά από την αρχή.
| Ανάγκη | Λύση στο SDK 7 |
|---|---|
| Απλό chat με streaming | streamText + useChat |
| Πολλαβλά βήματα με tools | Tool loop + stopWhen |
| Μακρά, ανθεκτική εκτέλεση | WorkflowAgent (@ai-sdk/workflow) |
| Coding agents (Claude Code, Codex, Pi) | HarnessAgent σε sandbox |
Το HarnessAgent επιστρέφει αποτελέσματα συμβατά με το AI SDK, άρα μπορεί να αντικαταστήσει τον agent σου χωρίς να αλλάξεις το UI με useChat. Τη στιγμή που γράφεται το άρθρο, οι πρώτες εκδόσεις του ήταν διαθέσιμες στο canary κανάλι, οπότε έλεγξε την κατάστασή του πριν το βάλεις σε production.
13 Πλήρες παράδειγμα WorkflowAgent
Ο στόχος: ένας agent αναζήτησης πτήσεων που επιβιώνει από restarts. Το WorkflowAgent τρέχει το ίδιο agent loop με το ToolLoopAgent, αλλά μέσα σε workflow, και αντικαθιστά το παλιό DurableAgent.
pnpm add @ai-sdk/workflow workflow ai zodΤο workflow δίνει το runtime (getWritable, οι οδηγίες 'use workflow' και 'use step'). Τα ai και zod είναι peer dependencies.
import { WorkflowAgent } from '@ai-sdk/workflow';
import { tool, isStepCount, type UIMessage } from 'ai';
import { getWritable } from 'workflow';
import { z } from 'zod';
const searchFlights = tool({
description: 'Ψάχνει πτήσεις',
inputSchema: z.object({ from: z.string(), to: z.string() }),
execute: async ({ from, to }) => {
'use step'; // durable κλήση: retries + αποθήκευση αποτελέσματος
const r = await fetch(`https://api.flights.example/search?from=${from}&to=${to}`);
return r.json();
},
});
export async function chatWorkflow(messages: UIMessage[]) {
'use workflow'; // η συνάρτηση γίνεται durable workflow
const agent = new WorkflowAgent({
model: 'anthropic/claude-sonnet-4.5',
instructions: 'Βοηθάς στην εύρεση πτήσεων.',
tools: { searchFlights },
stopWhen: isStepCount(10),
});
// Το stream γράφεται στο getWritable(). Δες στα docs την ακριβή
// υπογραφή του agent.stream() και τον μετασχηματισμό σε UI chunks.
}Στο frontend αντικαθιστάς το transport του useChat με το WorkflowChatTransport, που επανασυνδέεται αυτόματα στο stream αν κοπεί η σύνδεση ή γίνει refresh.
'use client';
import { useChat } from '@ai-sdk/react';
import { WorkflowChatTransport } from '@ai-sdk/workflow';
const { messages, sendMessage } = useChat({
transport: new WorkflowChatTransport({ api: '/api/chat' }),
});Retries
Κάθε tool με 'use step' έχει retries και δική του γραμμή στο workflow dashboard.
Έγκριση χρήστη
Ο agent παγώνει μέχρι να εγκρίνει ο άνθρωπος και συνεχίζει χωρίς να χάσει κατάσταση.
Serializable context
Στο context μπαίνουν μόνο δεδομένα, όχι functions ή clients βάσης. Δημιούργησέ τα μέσα στα steps.
sleep() ή createHook(), ΔΕΝ παίρνουν 'use step'. (2) Ό,τι δεν έχει 'use step' τρέχει ως κανονική συνάρτηση μνήμης, χωρίς εγγυήσεις durability. (3) Το maxSteps αντικαταστάθηκε από τα κοινά stop conditions, π.χ. isStepCount(10).14 Παρατήρηση και debugging
Στο SDK 6 έβαζες experimental_telemetry σε κάθε κλήση. Στο 7 το telemetry είναι ξεχωριστό και καταχωρείται μία φορά για όλες τις συναρτήσεις του SDK. Το @ai-sdk/otel εκπέμπει spans και metrics σύμφωνα με τα OpenTelemetry GenAI conventions, άρα συνδέεται με Datadog, Grafana, Honeycomb και όποιο OTel backend χρησιμοποιείς.
Τι μετράς
Latency ανά step, token usage, tool calls, σφάλματα.
Γιατί έχει σημασία
Ένας agent που «κολλάει» ή ξοδεύει tokens δεν φαίνεται χωρίς traces.
15 Σύνοψη αλλαγών του SDK 7
| Αλλαγή | Τι σημαίνει για σένα |
|---|---|
Νέο telemetry (@ai-sdk/otel) | Ρυθμίζεις το tracing μία φορά και καλύπτει όλες τις κλήσεις του SDK. |
WorkflowAgent | Agents που επιβιώνουν από restarts και τρέχουν για πολλή ώρα. |
HarnessAgent | Ενιαίο API για Claude Code, Codex, Pi, μέσα σε sandbox. |
| Top-level reasoning | Ρυθμίζεις το reasoning με ενιαίο τρόπο, χωρίς εξαρτήσεις από τον provider. |
| Provider-independent uploads | Το ίδιο αρχείο δουλεύει σε διαφορετικά μοντέλα. |
| Typed tool context | Το context που περνάς στα tools ελέγχεται από τον TypeScript compiler. |
| Granular timeouts | Ξεχωριστά όρια για συνολική διάρκεια, ανά step και ανά chunk. |
| ESM only | Χρειάζεται import. Το require() δεν υποστηρίζεται. |
16 Μετάβαση από το SDK 6
- Κάνε ενημέρωση:
pnpm add ai@latest - Τρέξε το codemod:
npx @ai-sdk/codemod v7 - Μετάτρεψε τα imports σε ESM αν χρειάζεται.
- Αντικατάστησε τα
experimental_telemetryμε τη νέα ρύθμιση telemetry. - Τρέξε τα tests και έλεγξε τα tool και streaming flows.
17 Checklist πριν το production
Ασφάλεια
Auth στο route, rate limits, ποτέ API keys στον client.
Κόστος
Όρια tokens και budgets ανά κλειδί στο AI Gateway.
Tools
Approvals για ευαίσθητες ενέργειες, validation στα inputs.
Observability
Traces και alerts για λάθη και αργά steps.
18 Συχνές ερωτήσεις
Πρέπει να χρησιμοποιήσω το AI Gateway;
Όχι. Μπορείς να καλέσεις απευθείας έναν provider με τα πακέτα του (π.χ. @ai-sdk/openai). Το Gateway προσθέτει ενιαίο κλειδί, routing και budgets, αλλά είναι προαιρετικό.
Δουλεύει εκτός Next.js;
Ναι. Το core λειτουργεί σε οποιοδήποτε Node runtime και υπάρχουν UI bindings και για άλλα frameworks. Το Next.js απλώς κάνει το tutorial πιο σύντομο.
Πώς αποφεύγω ατέρμονα tool loops;
Με stopWhen: isStepCount(n). Βάλε ρεαλιστικό όριο (3 έως 8) ανάλογα με το πόσα βήματα χρειάζεται πραγματικά ο agent σου.
Πού μπαίνουν τα API keys;
Μόνο στον server (env variables). Ο client μιλάει πάντα με το δικό σου route, ποτέ απευθείας με τον provider.
Μπορώ να δοκιμάσω έναν agent χωρίς να γράψω UI;
Ναι, με το @ai-sdk/tui τον τρέχεις σε διαδραστικό τερματικό και βλέπεις reasoning, tools και markdown πριν χτίσεις εφαρμογή.
19 Συμπέρασμα
Με λιγότερο από 50 γραμμές κώδικα έχεις chatbot με streaming και tools. Το SDK 7 προσθέτει όσα χρειάζονται όταν το demo γίνεται προϊόν: durability, approvals, telemetry και ευελιξία σε μοντέλα και agent harnesses. Ξεκίνα απλά με streamText και useChat και πρόσθεσε τα υπόλοιπα μόνο όταν τα χρειαστείς.
20 Σχετικά άρθρα & εργαλεία
Prompt Injection Playground
Δοκίμασε prompts ενάντια σε injection και jailbreaks πριν το production.