Open-source models¶
The previous chapters used proprietary models from OpenAI and Anthropic. There is also a rich ecosystem of open-source (more precisely, open-weight) models, such as DeepSeek, Qwen, and Llama. They are attractive for computational social science research for two reasons:
- Transparency and reproducibility: the model weights are public, so you can name the exact model version in your paper, and it will not be deprecated or changed under you.
- Cost: open models can be much cheaper than proprietary models of similar capability.
You don't need your own GPUs to use them.
Many providers host open models behind APIs.
This chapter uses OpenRouter, which gives you access to hundreds of models from different providers with a single API key.
Better yet, its API is compatible with OpenAI's SDK, so everything you learned in the earlier chapters carries over with a two-line change.
It also offers some free models, with the :free suffix in their IDs, so you can try this chapter without paying.
The free models have stricter rate limits and may change without notice.
Setup¶
Install the dependencies and configure your API key. You can create an API key in the OpenRouter settings.
On Google Colab, store your key in Colab Secrets first: click the key icon in the left sidebar, add a secret named OPENROUTER_API_KEY, and enable notebook access. Running locally, put the key in a .env file or in the OPENROUTER_API_KEY environment variable (see handling API keys).
%pip install -q openai pydantic python-dotenv
import os
from dotenv import load_dotenv
load_dotenv() # loads a .env file from the working directory, if there is one
if "OPENROUTER_API_KEY" not in os.environ:
try:
# On Google Colab, read the key from Colab Secrets
from google.colab import userdata
os.environ["OPENROUTER_API_KEY"] = userdata.get("OPENROUTER_API_KEY")
except ImportError:
raise RuntimeError("Set OPENROUTER_API_KEY in your environment or in a .env file")
Basic query¶
Let's run the same sentiment analysis example with an open model.
We use the same openai package, with two changes:
- Point the client at OpenRouter with the
base_urlparameter and pass the OpenRouter key. - OpenRouter uses the Chat Completions API instead of OpenAI's newer Responses API. The system prompt goes into the messages list as a message with the
systemrole, and the response text comes back incompletion.choices[0].message.content.
from openai import OpenAI
text_message = "The service here is very good!"
system_prompt = "You are an expert on sentiment analysis. Your job is to evaluate the sentiment of the given text message."
user_instruction = f"Given the following text message: '{text_message}', please evaluate its sentiment by giving a score in the range of -1 to 1, where -1 means negative and 1 means positive. Also explain why."
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
completion = client.chat.completions.create(
model="deepseek/deepseek-v4-flash",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_instruction},
],
)
print(completion.choices[0].message.content)
The sentiment score is **1.0**, indicating a very positive sentiment. The phrase "very good" is a strong positive descriptor, and the exclamation mark adds enthusiasm. There are no negative or neutral elements.
We use deepseek/deepseek-v4-flash in this chapter.
It is a cheap, capable open-weight model ($0.08 per 1M input tokens, $0.15 per 1M output tokens at the time of writing).
OpenRouter model IDs follow the creator/model-name pattern.
Browse the model catalog for alternatives; each open-weight model links to its public weights on Hugging Face.
Structured output¶
Structured output works here too, with the same Pydantic model as before.
On the Chat Completions API the method is client.chat.completions.parse, the model goes into the response_format parameter, and the parsed result comes back in completion.choices[0].message.parsed.
Note that structured output is supported by many, but not all, models on OpenRouter. You can filter the catalog for models that support it.
from pydantic import BaseModel, Field
class Sentiment(BaseModel):
score: float = Field(
ge=-1,
le=1,
description="Sentiment score in the range of -1 to 1, where -1 means negative and 1 means positive.",
)
explanation: str = Field(description="Explanation of the sentiment score.")
completion = client.chat.completions.parse(
model="deepseek/deepseek-v4-flash",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_instruction},
],
response_format=Sentiment,
)
parsed_output = completion.choices[0].message.parsed
# Print the result as a dictionary
print(parsed_output.model_dump())
# You can get the score and explanation directly
print(f"Score: {parsed_output.score}")
print(f"Explanation: {parsed_output.explanation}")
{'score': 1.0, 'explanation': "The message expresses strong satisfaction with the service, using the positive term 'very good' and an exclamation mark to convey enthusiasm."}
Score: 1.0
Explanation: The message expresses strong satisfaction with the service, using the positive term 'very good' and an exclamation mark to convey enthusiasm.
Scaling up¶
Because the client is the same openai package, the scaling techniques transfer directly: AsyncOpenAI with the same base_url gives you the async and threading patterns from the async programming chapter.
One more thing worth knowing: for the same model, OpenRouter may route your request to different hosting providers with different prices and speeds. The model catalog shows the providers behind each model, and you can pin one if reproducibility of the serving setup matters to you.