Working with LLM Responses
The get_llm_response function is the primary interface for calling language models in npcpy. It supports multiple providers, streaming, structured output, conversation history, and image attachments. This guide covers the function's capabilities and the shape of the data it returns.
Basic Usage
At minimum, provide a prompt, model, and provider.
from npcpy.llm_funcs import get_llm_response
response = get_llm_response(
"Who was the Celtic messenger god?",
model='qwen3:4b',
provider='ollama'
)
print(response['response'])
The Celtic messenger god is often associated with the figure of Lugh,
a deity of many skills who served as a messenger and champion among
the Tuatha De Danann in Irish mythology.
The Response Dict
Every call to get_llm_response returns a dict with a consistent structure:
{
'response': str | None, # The text reply, or None if tools were called
'raw_response': object, # The unprocessed provider response
'messages': list, # Full conversation history with the new exchange appended
'tool_calls': list, # Tool calls the LLM made (empty if none)
'tool_results': list, # Results from executing tools (empty if none)
}
The messages list is particularly useful for multi-turn conversations, as it includes the system message, all user prompts, and all assistant replies.
Streaming Responses
Set stream=True to get a generator instead of a complete string in response['response']. Use print_and_process_stream to consume it.
from npcpy.npc_sysenv import print_and_process_stream
from npcpy.llm_funcs import get_llm_response
response = get_llm_response(
"When did the United States government begin sending advisors to Vietnam?",
model='qwen3:latest',
provider='ollama',
stream=True
)
# response['response'] is a generator here -- print_and_process_stream
# iterates it, prints tokens as they arrive, and returns the full text
full_response = print_and_process_stream(
response['response'], 'qwen3:latest', 'ollama'
)
When streaming, the messages list in the response does not include the assistant reply automatically. You must append the completed text yourself if you want to continue the conversation.
Structured Output with format='json'
Request JSON output by setting format='json'. npcpy parses the string into a Python dict for you.
from npcpy.llm_funcs import get_llm_response
response = get_llm_response(
"What is the sentiment of the American people towards the repeal of "
"Roe v Wade? Return a JSON object with `sentiment` as the key and "
"a float value from -1 to 1 as the value.",
model='deepseek-chat',
provider='deepseek',
format='json'
)
print(response['response'])
This works with local models too:
response = get_llm_response(
"List the three largest cities in Colombia as a JSON array of objects "
"with 'name' and 'population' keys.",
model='llama3.2',
provider='ollama',
format='json'
)
print(response['response'])
[
{"name": "Bogota", "population": 7181469},
{"name": "Medellin", "population": 2569007},
{"name": "Cali", "population": 2227642}
]
Message History
Pass a messages list to continue a conversation. The function appends the new user prompt and assistant reply, then returns the updated list in the response.
from npcpy.llm_funcs import get_llm_response
messages = [{'role': 'system', 'content': 'You are a terse historian.'}]
# First turn
response = get_llm_response(
"When was the Battle of Boyaca?",
model='llama3.2',
provider='ollama',
messages=messages
)
print(response['response'])
# "August 7, 1819."
# Continue the conversation using the returned messages
messages = response['messages']
response = get_llm_response(
"Who commanded the republican forces?",
model='llama3.2',
provider='ollama',
messages=messages
)
print(response['response'])
# "Simon Bolivar."
Each call builds on the prior context. The messages list follows the standard format with role and content keys.
Attachments and Images
Pass image paths via the images parameter. Models with vision capabilities (like llava:7b) will process them.
from npcpy.llm_funcs import get_llm_response
messages = [{'role': 'system', 'content': 'You are an annoyed assistant.'}]
response = get_llm_response(
"What is in this image?",
model='llava:7b',
provider='ollama',
images=['./screenshot.png'],
messages=messages
)
print(response['response'])
The attachments parameter handles mixed file types -- images, PDFs, and CSVs. npcpy automatically detects the file type and processes it appropriately:
response = get_llm_response(
"Summarize this document and describe the chart.",
model='gemini-2.0-flash',
provider='gemini',
attachments=['./report.pdf', './chart.png']
)
AirLLM Provider
npcpy supports AirLLM for running large models with limited memory through layer-by-layer inference.
from npcpy.llm_funcs import get_llm_response
response = get_llm_response(
"Explain quantum entanglement in simple terms.",
model='Qwen/Qwen2.5-7B-Instruct',
provider='airllm'
)
print(response['response'])
AirLLM loads and processes model layers sequentially, allowing you to run models that would not fit entirely in memory. The trade-off is slower inference compared to fully-loaded models.
Provider Summary
get_llm_response routes to the appropriate backend based on the provider parameter:
| Provider | Example Model | Notes |
|---|---|---|
ollama |
llama3.2, gemma3:4b |
Local models, default provider |
openai |
gpt-4o, gpt-4o-mini |
Requires OPENAI_API_KEY |
anthropic |
claude-sonnet-4 |
Requires ANTHROPIC_API_KEY |
gemini |
gemini-2.0-flash |
Requires GEMINI_API_KEY |
deepseek |
deepseek-chat, deepseek-reasoner |
Requires DEEPSEEK_API_KEY |
airllm |
Qwen/Qwen2.5-7B-Instruct |
Layer-by-layer local inference |
lmstudio |
local model name | OpenAI-compatible on port 1234 |
llamacpp |
path to GGUF file | Direct llama.cpp binding |
transformers |
HuggingFace model ID | Full HuggingFace transformers |
lora |
path to adapter | LoRA fine-tuned adapters |
If no provider is specified and no NPC is given, npcpy defaults to ollama with llama3.2 (or llava:7b if images are present).