Improve Ollama speaker detection with multi-sample strategy
- Sample up to 4 chunks of 5000 characters from different text positions - Distribute samples evenly across beginning, middle, and end of text - Deduplicate speakers across all samples for comprehensive detection - Log individual sample results for debugging - Handle short texts efficiently (single sample) - Updated tests to verify multi-sample behavior This ensures speakers introduced anywhere in long texts are detected, not just those appearing in the first 2000 characters.
This commit is contained in:
@@ -34,37 +34,54 @@ class OllamaService:
|
||||
async def detect_speakers(self, text: str) -> List[str]:
|
||||
"""
|
||||
Sends text to Ollama for speaker detection.
|
||||
Samples up to 4 chunks of 5000 characters from different parts of the text
|
||||
to ensure comprehensive speaker detection in long texts.
|
||||
|
||||
Args:
|
||||
text: Input text to analyze for speakers
|
||||
|
||||
Returns:
|
||||
List of detected speaker names
|
||||
List of detected speaker names (deduplicated across all samples)
|
||||
|
||||
Raises:
|
||||
Exception: If Ollama is unavailable or request fails
|
||||
"""
|
||||
prompt = self._build_prompt(text)
|
||||
# Get text samples from different parts of the text
|
||||
samples = self._sample_text(text, chunk_size=5000, num_samples=4)
|
||||
|
||||
all_speakers = set()
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
response = await client.post(
|
||||
f"{self.base_url}/api/generate",
|
||||
json={
|
||||
"model": self.model,
|
||||
"prompt": prompt,
|
||||
"stream": False,
|
||||
"format": "json"
|
||||
}
|
||||
)
|
||||
response.raise_for_status()
|
||||
# Process each sample
|
||||
for i, sample in enumerate(samples):
|
||||
prompt = self._build_prompt(sample)
|
||||
|
||||
logger.debug(f"Sending sample {i+1}/{len(samples)} to Ollama ({len(sample)} chars)")
|
||||
|
||||
response = await client.post(
|
||||
f"{self.base_url}/api/generate",
|
||||
json={
|
||||
"model": self.model,
|
||||
"prompt": prompt,
|
||||
"stream": False,
|
||||
"format": "json"
|
||||
}
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
response_text = result.get("response", "")
|
||||
|
||||
speakers = self._parse_response(response_text)
|
||||
logger.info(f"Sample {i+1} detected {len(speakers)} speakers: {speakers}")
|
||||
|
||||
# Add to our set (automatically deduplicates)
|
||||
all_speakers.update(speakers)
|
||||
|
||||
result = response.json()
|
||||
response_text = result.get("response", "")
|
||||
|
||||
speakers = self._parse_response(response_text)
|
||||
logger.info(f"Ollama detected {len(speakers)} speakers: {speakers}")
|
||||
return speakers
|
||||
speaker_list = sorted(list(all_speakers))
|
||||
logger.info(f"Ollama detected {len(speaker_list)} total unique speakers: {speaker_list}")
|
||||
return speaker_list
|
||||
|
||||
except httpx.TimeoutException:
|
||||
logger.warning("Ollama request timed out after 30 seconds")
|
||||
@@ -76,6 +93,47 @@ class OllamaService:
|
||||
logger.warning(f"Ollama detection failed: {e}")
|
||||
raise
|
||||
|
||||
def _sample_text(self, text: str, chunk_size: int = 5000, num_samples: int = 4) -> List[str]:
|
||||
"""
|
||||
Samples text from different parts to ensure comprehensive speaker detection.
|
||||
|
||||
For short texts, returns the full text.
|
||||
For long texts, samples from beginning, middle sections, and end.
|
||||
|
||||
Args:
|
||||
text: Input text to sample
|
||||
chunk_size: Size of each sample in characters
|
||||
num_samples: Number of samples to extract
|
||||
|
||||
Returns:
|
||||
List of text samples
|
||||
"""
|
||||
text_len = len(text)
|
||||
|
||||
# If text is shorter than one chunk, return it as-is
|
||||
if text_len <= chunk_size:
|
||||
return [text]
|
||||
|
||||
# If text is shorter than what we'd sample, return fewer samples
|
||||
max_possible_samples = (text_len // chunk_size) + 1
|
||||
actual_samples = min(num_samples, max_possible_samples)
|
||||
|
||||
samples = []
|
||||
|
||||
if actual_samples == 1:
|
||||
# Just take the beginning
|
||||
samples.append(text[:chunk_size])
|
||||
else:
|
||||
# Distribute samples evenly across the text
|
||||
step = text_len // actual_samples
|
||||
|
||||
for i in range(actual_samples):
|
||||
start = i * step
|
||||
end = min(start + chunk_size, text_len)
|
||||
samples.append(text[start:end])
|
||||
|
||||
return samples
|
||||
|
||||
def _build_prompt(self, text: str) -> str:
|
||||
"""
|
||||
Constructs prompt for speaker detection.
|
||||
@@ -83,7 +141,7 @@ class OllamaService:
|
||||
Instructs model to return JSON array of speaker names.
|
||||
|
||||
Args:
|
||||
text: Input text to analyze
|
||||
text: Input text to analyze (should already be sampled/truncated)
|
||||
|
||||
Returns:
|
||||
Formatted prompt string
|
||||
@@ -98,7 +156,7 @@ Examples:
|
||||
- For mixed content, include both characters and "Narrator" if appropriate
|
||||
|
||||
Text to analyze:
|
||||
{text[:2000]}
|
||||
{text}
|
||||
|
||||
Return format: {{"speakers": ["Speaker1", "Speaker2", ...]}}
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user