Ollama timeout fix
2025-07-266 turns22,888 charsgpt-4o
Summary
User was debugging a Python script that was timing out when connecting to Ollama for text embeddings.
Messages
(venv) danielkliewer@Daniels-MacBook-Pro red-persona-01 % python3 personafy.py
🔍 Loading markdown content...
📝 Loaded 2521 text samples
📐 Embedding texts...
/Users/danielkliewer/red-persona-01/venv/lib/python3.13/site-packages/torch/nn/modules/module.py:1762: FutureWarning: `encoder_attention_mask` is deprecated and will be removed in version 4.55.0 for `BertSdpaSelfAttention.forward`.
return forward_call(*args, **kwargs)
🧠 Clustering into personas...
huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...
To disable this warning, you can either:
- Avoid using `tokenizers` before the fork if possible
- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)
huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...
To disable this warning, you can either:
- Avoid using `tokenizers` before the fork if possible
- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)
huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...
To disable this warning, you can either:
- Avoid using `tokenizers` before the fork if possible
- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)
huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...
To disable this warning, you can either:
- Avoid using `tokenizers` before the fork if possible
- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)
huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...
To disable this warning, you can either:
- Avoid using `tokenizers` before the fork if possible
- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)
huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...
To disable this warning, you can either:
- Avoid using `tokenizers` before the fork if possible
- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)
Traceback (most recent call last):
File "/Users/danielkliewer/red-persona-01/personafy.py", line 252, in <module>
main()
~~~~^^
File "/Users/danielkliewer/red-persona-01/personafy.py", line 244, in main
personas = generate_personas(texts, embeddings, NUM_CLUSTERS)
File "/Users/danielkliewer/red-persona-01/personafy.py", line 186, in generate_personas
summary_text = summarize_persona_local(short_samples)
File "/Users/danielkliewer/red-persona-01/personafy.py", line 165, in summarize_persona_local
result = subprocess.run(
["ollama", "run", OLLAMA_MODEL],
...<3 lines>...
timeout=60
)
File "/opt/homebrew/Cellar/python@3.13/3.13.5/Frameworks/Python.framework/Versions/3.13/lib/python3.13/subprocess.py", line 556, in run
stdout, stderr = process.communicate(input, timeout=timeout)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/python@3.13/3.13.5/Frameworks/Python.framework/Versions/3.13/lib/python3.13/subprocess.py", line 1222, in communicate
stdout, stderr = self._communicate(input, endtime, timeout)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/python@3.13/3.13.5/Frameworks/Python.framework/Versions/3.13/lib/python3.13/subprocess.py", line 2129, in _communicate
self._check_timeout(endtime, orig_timeout, stdout, stderr)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/python@3.13/3.13.5/Frameworks/Python.framework/Versions/3.13/lib/python3.13/subprocess.py", line 1269, in _check_timeout
raise TimeoutExpired(
...<2 lines>...
stderr=b''.join(stderr_seq) if stderr_seq else None)
subprocess.TimeoutExpired: Command '['ollama', 'run', 'mistral']' timed out after 60 seconds
The error message you're seeing indicates that the `subprocess.run()` call to the command:
```bash
ollama run mistral
```
is timing out after 60 seconds, leading to this exception:
```
subprocess.TimeoutExpired: Command '['ollama', 'run', 'mistral']' timed out after 60 seconds
```
### ✅ TL;DR Fix:
Increase the timeout or handle the subprocess more gracefully. You can modify `personafy.py` to raise the timeout or stream the process.
---
### ✅ Solution Options
#### **1. Increase the Timeout (Quick Fix)**
Find this in your script:
```python
result = subprocess.run(
["ollama", "run", OLLAMA_MODEL],
input=prompt.encode("utf-8"),
capture_output=True,
timeout=60
)
```
Change `timeout=60` to something like `timeout=300`:
```python
timeout=300 # Give it 5 minutes to respond
```
---
#### **2. Use `subprocess.Popen()` for Streaming Output (More Robust)**
If the model takes longer than expected or if it streams responses slowly, use `Popen` instead:
```python
import subprocess
def run_ollama_stream(prompt):
process = subprocess.Popen(
["ollama", "run", OLLAMA_MODEL],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
try:
stdout, stderr = process.communicate(prompt, timeout=300)
return stdout
except subprocess.TimeoutExpired:
process.kill()
print("❌ Timeout from Ollama model")
return None
```
Replace your `subprocess.run(...)` with:
```python
result = run_ollama_stream(prompt)
if result is None:
raise RuntimeError("Model timed out.")
```
---
### ✅ Optional: Avoid Deadlocks with Tokenizer Parallelism Warning
You're also seeing repeated messages about `huggingface/tokenizers`:
```text
huggingface/tokenizers: The current process just got forked...
```
To silence this warning, add this to the top of your script (before tokenization begins):
```python
import os
os.environ["TOKENIZERS_PARALLELISM"] = "false"
```
---
### ✅ Summary
1. **Timeout fix**: Increase the timeout to `300`.
2. **Better subprocess control**: Use `Popen` to handle longer or streaming outputs.
3. **Silence tokenizer warnings**: `os.environ["TOKENIZERS_PARALLELISM"] = "false"`
Let me know if you want help rewriting your `summarize_persona_local()` function with `Popen`.
gpt-4o
python3 personafy.py
🔍 Loading markdown content...
📝 Loaded 2521 text samples
📐 Embedding texts...
/Users/danielkliewer/red-persona-01/venv/lib/python3.13/site-packages/torch/nn/
modules/module.py:1762: FutureWarning: `encoder_attention_mask` is deprecated and will be removed in version 4.55.0 for `BertSdpaSelfAttention.forward`. return forward_call(*args, **kwargs)
🧠 Clustering into personas...
Traceback (most recent call last):
File "/Users/danielkliewer/red-persona-01/personafy.py", line 252, in <module
> main()
~~~~^^
File "/Users/danielkliewer/red-persona-01/personafy.py", line 244, in main
personas = generate_personas(texts, embeddings, NUM_CLUSTERS)
File "/Users/danielkliewer/red-persona-01/personafy.py", line 186, in generat
e_personas summary_text = summarize_persona_local(short_samples)
File "/Users/danielkliewer/red-persona-01/personafy.py", line 165, in summari
ze_persona_local result = subprocess.run(
["ollama", "run", OLLAMA_MODEL],
...<3 lines>...
timeout=300
)
File "/opt/homebrew/Cellar/python@3.13/3.13.5/Frameworks/Python.framework/Ver
sions/3.13/lib/python3.13/subprocess.py", line 556, in run stdout, stderr = process.communicate(input, timeout=timeout)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/python@3.13/3.13.5/Frameworks/Python.framework/Ver
sions/3.13/lib/python3.13/subprocess.py", line 1222, in communicate stdout, stderr = self._communicate(input, endtime, timeout)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/python@3.13/3.13.5/Frameworks/Python.framework/Ver
sions/3.13/lib/python3.13/subprocess.py", line 2129, in _communicate self._check_timeout(endtime, orig_timeout, stdout, stderr)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/python@3.13/3.13.5/Frameworks/Python.framework/Ver
sions/3.13/lib/python3.13/subprocess.py", line 1269, in _check_timeout raise TimeoutExpired(
...<2 lines>...
stderr=b''.join(stderr_seq) if stderr_seq else None)
subprocess.TimeoutExpired: Command '['ollama', 'run', 'mistral']' timed out aft
er 300 seconds
You're still getting a `TimeoutExpired` error, even after increasing the timeout to 300 seconds:
```
subprocess.TimeoutExpired: Command '['ollama', 'run', 'mistral']' timed out after 300 seconds
```
This means the `ollama run mistral` call is either **stalling**, **waiting for input**, or **taking too long to generate output**. Here's how to fix or debug this reliably:
---
### ✅ Fix Step-by-Step
#### 1. **Test the `ollama run` call outside Python**
Run this from your terminal:
```bash
echo "Summarize this sample text." | ollama run mistral
```
If it hangs or asks for more input, then Ollama may not be receiving the prompt correctly from your script. If it works normally, then we know the model is fine.
---
#### 2. **Check if Your Prompt is Too Long**
Print the prompt before passing it to subprocess:
```python
print(prompt)
print(f"Prompt length: {len(prompt)} characters")
```
If it's over ~8000 tokens (depending on the model), it might be silently failing.
---
#### 3. **Switch to `Popen` to Stream Output Safely**
Replace your `subprocess.run(...)` call with the safer `Popen` version. Here's a full working replacement for `summarize_persona_local`:
```python
import subprocess
def summarize_persona_local(samples):
prompt = "Summarize the following writing samples into a personality description:\n\n"
prompt += "\n\n".join(samples)
process = subprocess.Popen(
["ollama", "run", OLLAMA_MODEL],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
try:
stdout, stderr = process.communicate(input=prompt, timeout=600)
if stderr:
print("⚠️ stderr:", stderr)
if process.returncode != 0:
raise RuntimeError(f"Model failed: {stderr}")
return stdout.strip()
except subprocess.TimeoutExpired:
process.kill()
print("❌ Ollama model timed out.")
return "Timeout: Could not generate persona."
```
---
### ✅ Optional Enhancements
#### Avoid tokenizer warnings:
At the top of your script, add:
```python
import os
os.environ["TOKENIZERS_PARALLELISM"] = "false"
```
#### Log failures:
Add logging or write failed prompts to disk for reprocessing:
```python
with open("failed_prompt.txt", "w") as f:
f.write(prompt)
```
---
### 🔍 Summary
- You’re hitting a **timeout**, most likely from a **long or malformed prompt**.
- Use `subprocess.Popen()` to stream input/output and improve reliability.
- Test `ollama run` from the shell to verify model functionality.
- Log prompt lengths and contents to help debug.
Would you like help modifying the full `generate_personas()` or `summarize_persona_local()` function to include retry logic and debugging output?
gpt-4o
import os
import json
import random
import subprocess
from glob import glob
from collections import defaultdict
import numpy as np
from sentence_transformers import SentenceTransformer
from sklearn.cluster import KMeans
# ========== CONFIG ==========
BASE_DIR = "./reddit_data/KonradFreeman"
NUM_CLUSTERS = 10
OUTPUT_DIR = "./personas"
OLLAMA_MODEL = "mistral-small3.2" # your local LLM model
RANDOM_SEED = 42
# ============================
os.environ["TOKENIZERS_PARALLELISM"] = "false"
def load_markdown_texts(base_dir):
files = glob(os.path.join(base_dir, "**/*.md"), recursive=True)
texts = []
for file in files:
with open(file, 'r', encoding='utf-8') as f:
content = f.read()
if len(content.strip()) > 50:
texts.append((file, content.strip()))
return texts
def embed_texts(texts):
model = SentenceTransformer('all-MiniLM-L6-v2')
contents = [text for _, text in texts]
embeddings = model.encode(contents)
return embeddings
def cluster_texts(embeddings, num_clusters):
kmeans = KMeans(n_clusters=num_clusters, random_state=RANDOM_SEED)
labels = kmeans.fit_predict(embeddings)
return labels
def summarize_persona_local(text_samples):
joined_samples = "\n\n".join(text_samples)
prompt = f"""
You are analyzing a Reddit user's writing style and personality based on 5 sample posts/comments.
For each of the following 50 traits, rate how strongly that trait is expressed in these samples on a scale from 0.0 to 1.0, where 0.0 means "not present at all" and 1.0 means "strongly present and dominant".
Please output the results as a JSON object with keys as the trait names and values as floating point numbers between 0 and 1, inclusive.
The traits and what they measure:
1. openness: curiosity and creativity in ideas.
2. conscientiousness: carefulness and discipline.
3. extraversion: sociability and expressiveness.
4. agreeableness: kindness and cooperativeness.
5. neuroticism: emotional instability or sensitivity.
6. optimism: hopeful and positive tone.
7. skepticism: questioning and critical thinking.
8. humor: presence of irony, wit, or jokes.
9. formality: use of formal language and structure.
10. emotionality: expression of feelings and passion.
11. analytical: logical reasoning and argumentation.
12. narrative: storytelling and personal anecdotes.
13. philosophical: discussion of abstract ideas.
14. political: engagement with political topics.
15. technical: use of technical or domain-specific language.
16. empathy: understanding others' feelings.
17. assertiveness: confident and direct expression.
18. humility: modesty and openness to other views.
19. creativity: original and novel expressions.
20. negativity: presence of criticism or complaints.
21. curiosity: eagerness to explore and learn.
22. frustration: signs of irritation or dissatisfaction.
23. supportiveness: encouraging and helpful tone.
24. introspection: self-reflection and personal insight.
25. irony: use of sarcasm or indirect humor.
26. self-deprecation: making fun of oneself.
27. rebelliousness: resistance to authority or norms.
28. clarity: how clear and understandable the writing is.
29. verbosity: tendency to use many words.
30. conciseness: brevity and efficiency in expression.
31. abstraction: use of conceptual, vague, or intangible ideas.
32. concreteness: use of tangible and specific examples.
33. poeticness: lyrical or artistic expression.
34. intensity: forcefulness or emotional strength.
35. dry_humor: understated and deadpan comedic tone.
36. wit: clever and quick verbal humor.
37. playfulness: light, spontaneous tone or wordplay.
38. moralizing: emphasis on right and wrong.
39. storytelling_depth: complexity and layers in narratives.
40. irony_detection: ability to recognize and deploy irony.
41. cultural_reference: use of shared cultural, media, or historic touchpoints.
42. emotional_regulation: control over emotional expression.
43. antagonism: tendency toward conflict or provocation.
44. warmth: friendliness and emotional accessibility.
45. idealism: belief in or striving for perfection or betterment.
46. realism: grounded and practical perspective.
47. sarcasm: biting or mocking form of humor.
48. absurdism: surreal or illogical humor and concepts.
49. urgency: immediacy or emotional pressure in tone.
50. rhythm: sense of pacing and flow in the writing.
Analyze these samples carefully and output the JSON exactly like this example (with different values):
{{
"openness": 0.75,
"conscientiousness": 0.55,
"extraversion": 0.10,
"agreeableness": 0.60,
"neuroticism": 0.20,
"optimism": 0.50,
"skepticism": 0.85,
"humor": 0.15,
"formality": 0.30,
"emotionality": 0.70,
"analytical": 0.80,
"narrative": 0.45,
"philosophical": 0.65,
"political": 0.40,
"technical": 0.25,
"empathy": 0.55,
"assertiveness": 0.35,
"humility": 0.50,
"creativity": 0.60,
"negativity": 0.10,
"curiosity": 0.70,
"frustration": 0.05,
"supportiveness": 0.40,
"introspection": 0.75,
"irony": 0.30,
"self_deprecation": 0.20,
"rebelliousness": 0.65,
"clarity": 0.90,
"verbosity": 0.80,
"conciseness": 0.40,
"abstraction": 0.55,
"concreteness": 0.60,
"poeticness": 0.25,
"intensity": 0.75,
"dry_humor": 0.50,
"wit": 0.70,
"playfulness": 0.65,
"moralizing": 0.35,
"storytelling_depth": 0.60,
"irony_detection": 0.45,
"cultural_reference": 0.50,
"emotional_regulation": 0.55,
"antagonism": 0.20,
"warmth": 0.60,
"idealism": 0.45,
"realism": 0.50,
"sarcasm": 0.55,
"absurdism": 0.40,
"urgency": 0.35,
"rhythm": 0.80
}}
{joined_samples}
"""
result = subprocess.run(
["ollama", "run", OLLAMA_MODEL],
input=prompt,
capture_output=True,
text=True,
timeout=300
)
return result.stdout.strip() # <- Return raw string, no parsing
def generate_personas(texts, embeddings, num_clusters):
labels = cluster_texts(embeddings, num_clusters)
clusters = defaultdict(list)
for (filename, content), label in zip(texts, labels):
clusters[label].append(content)
personas = []
for label, samples in clusters.items():
short_samples = random.sample(samples, min(5, len(samples)))
summary_text = summarize_persona_local(short_samples)
persona = {
"id": label,
"summary": summary_text,
"samples": short_samples
}
personas.append(persona)
return personas
def convert_numpy(obj):
if isinstance(obj, dict):
return {k: convert_numpy(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [convert_numpy(i) for i in obj]
elif isinstance(obj, (np.integer,)):
return int(obj)
elif isinstance(obj, (np.floating,)):
return float(obj)
else:
return obj
def save_personas(personas, output_dir):
os.makedirs(output_dir, exist_ok=True)
for i, persona in enumerate(personas):
with open(f"{output_dir}/persona_{i}.json", "w") as f:
# If any values are NumPy or other types, convert to plain Python types
cleaned = {
k: float(v) if hasattr(v, 'item') else v
for k, v in persona.items()
}
json.dump(cleaned, f, indent=2)
def convert_to_serializable(obj):
if isinstance(obj, dict):
return {k: convert_to_serializable(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [convert_to_serializable(i) for i in obj]
elif isinstance(obj, (np.integer, np.floating)):
return obj.item() # Convert to native Python int/float
else:
return obj
def main():
print("🔍 Loading markdown content...")
texts = load_markdown_texts(BASE_DIR)
print(f"📝 Loaded {len(texts)} text samples")
print("📐 Embedding texts...")
embeddings = embed_texts(texts)
print("🧠 Clustering into personas...")
personas = generate_personas(texts, embeddings, NUM_CLUSTERS)
print("💾 Saving personas...")
save_personas(personas, OUTPUT_DIR)
print("✅ Done. Personas saved to", OUTPUT_DIR)
if __name__ == "__main__":
main()
Thanks for sharing the full code — it's solid and well-structured, but the issue is clearly with how `subprocess.run()` interacts with `ollama` when generating responses. Since `ollama run mistral-small3.2` is still timing out (even after 300s), let's make it *resilient* by switching to `Popen` to stream and control the output more safely.
---
### ✅ Here's a Fixed Version of `summarize_persona_local()` Using `subprocess.Popen`:
Replace your existing `summarize_persona_local()` function with this version:
```python
def summarize_persona_local(text_samples):
joined_samples = "\n\n".join(text_samples)
prompt = f"""
You are analyzing a Reddit user's writing style and personality based on 5 sample posts/comments.
For each of the following 50 traits, rate how strongly that trait is expressed in these samples on a scale from 0.0 to 1.0...
[TRUNCATED FOR BREVITY IN THIS RESPONSE — KEEP FULL PROMPT IN YOUR CODE]
{joined_samples}
"""
try:
process = subprocess.Popen(
["ollama", "run", OLLAMA_MODEL],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
stdout, stderr = process.communicate(input=prompt, timeout=600) # bump to 10 min just in case
if stderr:
print("⚠️ Ollama stderr:", stderr)
if process.returncode != 0:
print("❌ Ollama returned non-zero exit code")
return "{}"
return stdout.strip()
except subprocess.TimeoutExpired:
process.kill()
print("❌ Ollama timed out while summarizing persona.")
return "{}"
```
---
### 🔍 Explanation
- `subprocess.run()` is **blocking** and assumes the command will finish quickly. It's brittle when the model:
- Needs more than 5 minutes
- Hangs on partial input
- Writes a lot to `stdout` or `stderr`
- `subprocess.Popen()` gives you more control, especially:
- Killing the process if it hangs
- Capturing both `stdout` and `stderr`
- Streaming input
---
### 🛠 Optional Debugging Helpers
Add this just before returning in `summarize_persona_local()` to debug a stuck response:
```python
print("🧾 Raw Ollama Output:")
print(stdout[:500]) # print first 500 characters of model output
```
And if you want to write failed prompts to a file:
```python
if process.returncode != 0 or not stdout.strip().startswith("{"):
with open("failed_prompt_debug.txt", "w") as f:
f.write(prompt)
```
---
### ✅ Final Notes
Once you update to use `Popen`, this should:
- Fix your `TimeoutExpired` issue.
- Improve fault tolerance.
- Make debugging easier.
Let me know if you'd like to parallelize this with `multiprocessing` next or cache outputs to avoid reprocessing the same clusters.
gpt-4o