{"slug": "thursdays-with-koog-providers-and-models", "title": "Thursdays with Koog: Providers and Models", "summary": "Mark Murphy's Knosh coding agent maps model strings from Markdown frontmatter to Koog's LLMProvider and LLMClient objects, supporting providers including Ollama, Anthropic, Mistral, and OpenAI. The mapping is handled by a descriptors roster in Knosh's AgentConfig, which pairs provider IDs with client factories and API key configurations.", "body_md": "Last week, I posted [the inaugural \"Thursdays with Koog\"](https://pac.commonsware.com/archive/thursdays-with-koog-the-basics/), exploring the basics of how [Knosh](https://codeberg.org/commonsguy/knosh) (my one-shot coding agent) interacts with [Koog](https://docs.koog.ai/) (JetBrains' LLM interaction framework).\n\nIn that post, I showed that:\n\n```\nknosh prompt --agentId=general \"What can you do for me?\"\n```\n\n...causes Knosh to look up the definition of the `general`\n\nagent, which is encoded in Markdown:\n\n```\n---\nmodel: \"ollama/qwen3.6:35b-a3b-coding-nvfp4\"\ndescription: a general-purpose agent with full tool access\n---\nYou are a helpful assistant to an experienced software developer.\n```\n\nBut, somewhere along the line, we need to take that `model`\n\nand teach Koog that it represents what LLM we want to use. The left side (`ollama`\n\n) represents Koog's provider ID, while the right side (`qwen3.6:35b-a3b-coding-nvfp4`\n\n) represents the model to use with that provider... but we need to map those strings to Koog objects.\n\nKnosh parses that Markdown into [an AgentConfig](https://codeberg.org/commonsguy/knosh/src/tag/0.2.0/lib/knosh-agents/src/main/kotlin/com/commonsware/knosh/agents/AgentConfig.kt), with\n\n`modelProvider`\n\nand `modelName`\n\nholding those two pieces of the `model`\n\nfrontmatter property from the Markdown. We use the `modelProvider`\n\nto find our provider in a `descriptors`\n\nroster that is based on which Koog providers Knosh supports:\n\n```\n  private val descriptors: List<ProviderDescriptor> =\n    listOf(\n      ProviderDescriptor(\n        configName = \"ollama\",\n        provider = LLMProvider.Ollama,\n        modelDefinitions = null,\n        envVarName = null,\n        configKeyName = null,\n        readKey = { null },\n        createClient = { _, knoshConfig ->\n          OllamaClient(httpClientFactory = this.ollamaHttpClientFactory, baseUrl = knoshConfig.ollamaURL)\n        },\n      ),\n      ProviderDescriptor(\n        configName = \"anthropic\",\n        provider = LLMProvider.Anthropic,\n        modelDefinitions = AnthropicModels,\n        envVarName = \"ANTHROPIC_API_KEY\",\n        configKeyName = \"anthropicApiKey\",\n        readKey = { it.anthropicApiKey },\n        createClient = { apiKey, _ -> AnthropicLLMClient(apiKey) },\n      ),\n      ProviderDescriptor(\n        configName = \"mistral\",\n        provider = LLMProvider.MistralAI,\n        modelDefinitions = MistralAIModels,\n        envVarName = \"MISTRAL_API_KEY\",\n        configKeyName = \"mistralApiKey\",\n        readKey = { it.mistralApiKey },\n        createClient = { apiKey, _ -> MistralAILLMClient(apiKey) },\n      ),\n      ProviderDescriptor(\n        configName = \"openai\",\n        provider = LLMProvider.OpenAI,\n        modelDefinitions = OpenAIModels,\n        envVarName = \"OPENAI_API_KEY\",\n        configKeyName = \"openAiApiKey\",\n        readKey = { it.openAiApiKey },\n        createClient = { apiKey, _ -> OpenAILLMClient(apiKey) },\n      ),\n    )\n```\n\n[ LLMProvider](https://api.koog.ai/prompt/prompt-llm/ai.koog.prompt.llm/-l-l-m-provider/index.html?query=open%20class%20LLMProvider(val%20id:%20String,%20val%20display:%20String)) is Koog's representation of an LLM provider (sometimes, naming is actually easy!). Koog ships with a series of providers, a mix of dedicated model houses (OpenAI, Anthropic, etc.) and service providers (e.g., Amazon Bedrock, OpenRouter). However,\n\n`LLMProvider`\n\nis just a identifier and a display name — it houses no real business logic.That lands in [ LLMClient](https://api.koog.ai/prompt/prompt-executor/prompt-executor-clients/ai.koog.prompt.executor.clients/-l-l-m-client/index.html?query=expect%20abstract%20class%20LLMClient%20:%20LLMClientAPI,%20LLMEmbeddingProviderAPI) implementations, one per provider. These know how to talk the specific Web service (or whatever) API to the provider and use that to send prompts and get responses. These get supplied by specific Koog dependencies, so you can load in just the provider(s) that your app intends to support. Many of these clients require an API key; some of the properties on Knosh's\n\n`ProviderDescriptor`\n\nsay where and how to look up the API key to use.Those give us information about a provider. We also need to get a Koog object representing the model. That is an [ LLModel](https://api.koog.ai/prompt/prompt-llm/ai.koog.prompt.llm/-l-l-model/index.html?query=data%20class%20LLModel%C2%A0constructor(val%20provider:%20LLMProvider,%20val%20id:%20String,%20val%20capabilities:%20List%3CLLMCapability%3E?%20=%20null,%20val%20contextLength:%20Long?%20=%20null,%20val%20maxOutputTokens:%20Long?%20=%20null)), which ties a model name (e.g.,\n\n`claude-haiku-4-5`\n\n) and a provider together, along with some operational information about that model:Many providers will have an [ LLModelDefinitions](https://api.koog.ai/prompt/prompt-executor/prompt-executor-clients/ai.koog.prompt.executor.clients/-l-l-model-definitions/index.html?query=interface%20LLModelDefinitions), which amounts to a list of\n\n`LLModel`\n\ninstances, representing the known models for that provider at the time that particular version of Koog shipped. For example, `1.0.0`\n\nof the Anthropic Koog library does not know about Sonnet 5, Fable, etc., as those were released by Anthropic after Koog released `1.0.0`\n\n.To fill in the gaps, you are welcome to construct your own `LLModel`\n\ndefinitions. That is also needed for providers where there is no canonical roster of supported models. In the case of Knosh, that's important for Ollama support, as what models Ollama has depends on what you had Ollama download and install.\n\nSo, Knosh will try to find a matching `LLModel`\n\nfrom the `LLModelDefinitions`\n\nfor the provider, and if that fails, it builds its own, limiting the capabilities to what Knosh needs:\n\n```\nprivate fun resolveModel(\n  provider: LLMProvider,\n  modelId: String,\n  fallbackContextLength: Long,\n  modelDefinitions: LLModelDefinitions?,\n): LLModel =\n  modelDefinitions?.models?.find { it.id == modelId }\n    ?: LLModel(\n      provider = provider,\n      id = modelId,\n      capabilities =\n        listOf(\n          LLMCapability.Completion,\n          LLMCapability.Temperature,\n          LLMCapability.Schema.JSON.Basic,\n          LLMCapability.Tools,\n        ),\n      contextLength = fallbackContextLength,\n    )\n```\n\nGiven the `LLMProvider`\n\n, the `LLModel`\n\n, and the `LLMClient`\n\n, you are in position to start executing prompts... which we will explore in next week's \"Thursdays with Koog\" post.", "url": "https://wpnews.pro/news/thursdays-with-koog-providers-and-models", "canonical_source": "https://pac.commonsware.com/archive/thursdays-with-koog-providers-and-models/", "published_at": "2026-07-09 13:00:00+00:00", "updated_at": "2026-08-03 07:30:50.029739+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "large-language-models"], "entities": ["Mark Murphy", "Knosh", "Koog", "JetBrains", "Ollama", "Anthropic", "Mistral", "OpenAI"], "alternates": {"html": "https://wpnews.pro/news/thursdays-with-koog-providers-and-models", "markdown": "https://wpnews.pro/news/thursdays-with-koog-providers-and-models.md", "text": "https://wpnews.pro/news/thursdays-with-koog-providers-and-models.txt", "jsonld": "https://wpnews.pro/news/thursdays-with-koog-providers-and-models.jsonld"}}