In Part 1, we built the foundation of our AI Dream Analysis Engine. We accepted user input, cleaned the text, performed basic NLP tasks, and detected dream symbols. However, recognizing words like snake, water, or mountain isn't enough. The real intelligence begins when the system understands context, emotions, and relationships.
In this part, we'll build the core AI pipeline by integrating a Large Language Model (LLM), designing production-ready prompts, implementing Retrieval-Augmented Generation (RAG), generating embeddings, and returning structured JSON responses instead of plain text.
Many developers build AI applications by sending a user's text directly to GPT.
const response = await openai.chat.completions.create({
model: "gpt-4.1",
messages: [
{
role: "user",
content: dream
}
]
});
This works—but it's not reliable enough for production.
Problems include:
Instead of asking GPT to "interpret this dream," we should provide structured instructions and supporting knowledge.
Instead of a single API call, the workflow becomes:
User Dream
│
▼
Text Preprocessing
│
▼
Symbol Detection
│
▼
Emotion Analysis
│
▼
Embedding Generation
│
▼
Vector Search (Knowledge Base)
│
▼
Prompt Assembly
│
▼
GPT-4.1
│
▼
Structured JSON Response
Each stage adds context before the model generates its interpretation.
First, install the OpenAI SDK.
npm install openai
Create a reusable OpenAI client.
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
export default openai;
Keeping the client in a separate file allows the application to reuse a single connection.
Prompt engineering is often underestimated.
Consider this prompt.
Interpret this dream.
The AI has almost no guidance.
Now compare it with a structured system prompt.
const systemPrompt = `
You are an AI Dream Analysis Assistant.
Your responsibilities:
- Identify important dream symbols.
- Analyze emotions.
- Detect recurring themes.
- Explain reasoning.
- Avoid predicting the future.
- Avoid supernatural certainty.
- Present multiple interpretations.
- Mention uncertainty where appropriate.
Return JSON only.
`;
Notice that the prompt defines behavior, not just the task.
Now combine the prompt with the user's dream.
const completion = await openai.chat.completions.create({
model: "gpt-4.1",
temperature: 0.3,
messages: [
{
role: "system",
content: systemPrompt
},
{
role: "user",
content: dream
}
]
});
A lower temperature makes responses more consistent.
For dream analysis, consistency is generally preferable to creativity.
Many AI applications ask GPT to write paragraphs.
That's difficult for software to process.
Instead, ask for JSON.
Example output:
{
"summary":"The dream may reflect emotional transition.",
"symbols":[
"Snake",
"Mountain"
],
"emotion":"Fear",
"themes":[
"Growth",
"Anxiety"
],
"confidence":0.88
}
JSON allows your frontend to display each section separately.
const analysis = JSON.parse(
completion.choices[0].message.content
);
console.log(analysis.summary);
Instead of displaying one large paragraph, your application can render:
independently.
Dreams often contain similar meanings expressed in different words.
Example 1
I was terrified by a snake.
Example 2
A serpent chased me.
Humans recognize these as similar.
Computers do not.
This is where embeddings become useful.
Embeddings convert text into numerical vectors that represent semantic meaning.
const embedding = await openai.embeddings.create({
model:"text-embedding-3-large",
input:dream
});
The returned vector might contain thousands of numbers.
[0.024,
-0.103,
0.887,
...
1536 values]
These vectors allow similar dreams to be compared mathematically.
Traditional databases search exact words.
Vector databases search meaning.
Suppose users write:
Dream A
Snake chased me.
Dream B
A serpent followed me.
A SQL search may treat these as different.
A vector database understands their similarity.
Popular vector databases include:
Example using Pinecone.
await index.upsert([
{
id:userId,
values:embedding.data[0].embedding,
metadata:{
dream
}
}
]);
Now every analyzed dream becomes searchable.
Later, another user submits a new dream.
Generate its embedding.
Search the database.
const results = await index.query({
vector:embedding.data[0].embedding,
topK:5,
includeMetadata:true
});
Instead of relying entirely on GPT's memory, we now retrieve similar dream records.
RAG combines search with language models.
Workflow:
Dream
↓
Generate Embedding
↓
Search Vector Database
↓
Retrieve Similar Dreams
↓
Retrieve Symbol Articles
↓
Send Everything to GPT
↓
Generate Interpretation
Rather than answering from memory alone, GPT receives supporting information first.
This improves consistency and reduces hallucinations.
A dream engine should maintain trusted information about common symbols.
Example:
{
"snake":[
"Transformation",
"Fear",
"Healing",
"Renewal"
],
"water":[
"Emotion",
"Healing",
"Uncertainty"
]
}
The LLM uses this information instead of inventing unsupported meanings.
Prompt example:
Dream
"I dreamed a snake chased me."
Relevant Knowledge
Snake
Possible meanings:
Transformation
Fear
Renewal
Healing
Now analyze the dream using only the information above and explain your reasoning.
The model becomes grounded in trusted data.
Symbols alone don't explain dreams.
Emotion matters.
Example:
const emotions = [
"fear",
"joy",
"sadness",
"anger",
"hope",
"anxiety"
];
GPT can identify emotional patterns.
Example response:
{
"emotion":"Fear",
"intensity":"High"
}
Emotion often influences interpretation more than symbolism.
Instead of displaying raw AI text, create a structured report.
{
"summary":"The dream reflects emotional uncertainty.",
"symbols":[
"Snake",
"Forest"
],
"emotion":"Fear",
"themes":[
"Transformation",
"Personal Growth"
],
"recommendation":"Reflect on recent life changes.",
"confidence":91
}
This format is easier to display on websites, mobile apps, and APIs.
Hallucinations are one of the biggest production challenges.
Several strategies help reduce them.
1. Strong System Prompts. Clearly define rules.
2. Retrieval-Augmented Generation. Provide supporting information.
3. Lower Temperature. Use values between 0.2 and 0.4.
4. Return JSON. Structured outputs reduce randomness.
5. Validate Responses. Reject malformed JSON before displaying results.
Example:
try{
const analysis = JSON.parse(response);
}catch{
throw new Error("Invalid AI response.");
}
Putting everything together:
async function analyzeDream(dream){
validateDream(dream);
const cleaned = preprocess(dream);
const embedding = await createEmbedding(cleaned);
const relatedDreams = await searchVectorDB(embedding);
const prompt = buildPrompt(
cleaned,
relatedDreams
);
const result = await askGPT(prompt);
return JSON.parse(result);
}
Notice that GPT is only one component of the pipeline.
The surrounding engineering makes the system reliable.
So far, our engine can:
In Part 3, we'll move beyond AI and build the production infrastructure behind the application. We'll design a PostgreSQL database, create REST APIs, implement JWT authentication, save dream history, optimize performance with caching, and prepare the system for deployment.
By the end of the series, we'll have a complete blueprint for building a scalable AI-powered dream analysis platform rather than just another chatbot wrapped around an LLM.