Module: PWN::AI::Ollama
- Defined in:
- lib/pwn/ai/ollama.rb
Overview
Direct client for a local/remote Ollama server REST API. No API key is required for a stock ollama serve (http://127.0.0.1:11434). Paths are native Ollama:
GET /api/tags
POST /api/chat (native tool_calls, options.num_ctx / num_predict)
POST /api/embed
POST /v1/chat/completions (OpenAI-compat shim)
Spec: https://github.com/ollama/ollama/blob/main/docs/api.md
Constant Summary collapse
- DEFAULT_BASE_URI =
'http://127.0.0.1:11434'
Class Method Summary collapse
-
.authors ⇒ Object
- Author(s)
0day Inc.
-
.chat(opts = {}) ⇒ Object
- Supported Method Parameters
response = PWN::AI::Ollama.chat( request: 'required - message to Ollama' model: 'optional - model to use for text generation (defaults to PWN::Env[:ollama][:model])', temp: 'optional - creative response float (defaults to PWN::Env[:ollama][:temp])', system_role_content: 'optional - context to set up the model behavior for conversation (Default: PWN::Env[:ollama][:system_role_content])', response_history: 'optional - pass response back in to have a conversation', speak_answer: 'optional speak answer using PWN::Plugins::Voice.text_to_speech (Default: nil)', timeout: 'optional timeout in seconds (defaults to 900)', spinner: 'optional - display spinner (defaults to false)' ).
-
.chat_with_tools(opts = {}) ⇒ Object
- Supported Method Parameters
response = PWN::AI::Ollama.chat_with_tools( messages: 'required - full OpenAI-format messages array (system/user/assistant/tool)', tools: 'optional - OpenAI tools array [function:{...}]', tool_choice: 'optional - "auto" | "none" | function:{name:..}', model: 'optional - overrides PWN::Env[:ollama][:model]', temp: 'optional - temperature (defaults to PWN::Env[:ollama][:temp] || 1)', timeout: 'optional - seconds (default 900)', spinner: 'optional - display spinner (default false)' ).
-
.get_models ⇒ Object
- Supported Method Parameters
response = PWN::AI::Ollama.get_models.
-
.get_plan_usage(opts = {}) ⇒ Object
- Supported Method Parameters
usage = PWN::AI::Ollama.get_plan_usage.
-
.help ⇒ Object
Display Usage for this Module.
Class Method Details
.authors ⇒ Object
- Author(s)
0day Inc. support@0dayinc.com
647 648 649 650 651 |
# File 'lib/pwn/ai/ollama.rb', line 647 public_class_method def self. "AUTHOR(S): 0day Inc. <support@0dayinc.com> " end |
.chat(opts = {}) ⇒ Object
- Supported Method Parameters
response = PWN::AI::Ollama.chat( request: 'required - message to Ollama' model: 'optional - model to use for text generation (defaults to PWN::Env[:ollama][:model])', temp: 'optional - creative response float (defaults to PWN::Env[:ollama][:temp])', system_role_content: 'optional - context to set up the model behavior for conversation (Default: PWN::Env[:ollama][:system_role_content])', response_history: 'optional - pass response back in to have a conversation', speak_answer: 'optional speak answer using PWN::Plugins::Voice.text_to_speech (Default: nil)', timeout: 'optional timeout in seconds (defaults to 900)', spinner: 'optional - display spinner (defaults to false)' )
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 |
# File 'lib/pwn/ai/ollama.rb', line 554 public_class_method def self.chat(opts = {}) engine = PWN::Env[:ai][:ollama] request = opts[:request] max_prompt_length = engine[:max_prompt_length] ||= 1_000_000 request_trunc_idx = ((max_prompt_length - 1) / 3.36).floor request = request[0..request_trunc_idx] model = opts[:model] ||= engine[:model] raise 'ERROR: Model is required. Call #get_models method for details' if model.nil? temp = opts[:temp].to_f ||= engine[:temp].to_f temp = 1 if temp.zero? # Native Ollama chat. Stock ollama serve and many reverse proxies # expose /api/chat, not the OpenAI-compat shim /v1/chat/completions # (that path 404s when the shim is disabled or not mounted). rest_call = 'api/chat' response_history = opts[:response_history] system_role_content = opts[:system_role_content] ||= engine[:system_role_content] system_role = { role: 'system', content: system_role_content } user_role = { role: 'user', content: request } response_history ||= { choices: [system_role] } num_ctx = (engine[:num_ctx] || 32_768).to_i num_predict = (engine[:num_predict] || 4_096).to_i keep_alive = engine[:keep_alive] || '30m' http_body = { model: model, messages: [system_role], stream: true, keep_alive: keep_alive, options: { num_ctx: num_ctx, num_predict: num_predict, temperature: temp } } if response_history[:choices].length > 1 response_history[:choices][1..-1].each do || http_body[:messages].push() end end http_body[:messages].push(user_role) timeout = opts[:timeout] spinner = opts[:spinner] response = ollama_rest_call( http_method: :post, rest_call: rest_call, http_body: http_body, timeout: timeout, spinner: spinner ) json_resp = JSON.parse(response, symbolize_names: true) assistant_resp = json_resp[:message] || json_resp.dig(:choices, 0, :message) raise "ERROR: Ollama chat response missing message/choices: #{json_resp.inspect[0, 400]}" if assistant_resp.nil? json_resp[:choices] = http_body[:messages] json_resp[:choices].push(assistant_resp) speak_answer = true if opts[:speak_answer] if speak_answer answer = assistant_resp[:content] text_path = "/tmp/#{SecureRandom.hex}.pwn_voice" File.write(text_path, answer) PWN::Plugins::Voice.text_to_speech(text_path: text_path) File.unlink(text_path) end json_resp rescue StandardError => e raise e end |
.chat_with_tools(opts = {}) ⇒ Object
- Supported Method Parameters
response = PWN::AI::Ollama.chat_with_tools( messages: 'required - full OpenAI-format messages array (system/user/assistant/tool)', tools: 'optional - OpenAI tools array [function:{...}]', tool_choice: 'optional - "auto" | "none" | function:{name:..}', model: 'optional - overrides PWN::Env[:ollama][:model]', temp: 'optional - temperature (defaults to PWN::Env[:ollama][:temp] || 1)', timeout: 'optional - seconds (default 900)', spinner: 'optional - display spinner (default false)' )
Hits Ollama NATIVE POST /api/chat so options.num_ctx / num_predict / keep_alive take effect. Streaming is ON; ollama_rest_call assembles NDJSON chunks back into a single response.
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 |
# File 'lib/pwn/ai/ollama.rb', line 481 public_class_method def self.chat_with_tools(opts = {}) engine = PWN::Env[:ai][:ollama] = (messages: opts[:messages]) raise 'ERROR: messages array is required' if .nil? || .empty? model = opts[:model] ||= engine[:model] raise 'ERROR: Model is required. Call #get_models method for details' if model.nil? temp = opts[:temp].to_f temp = engine[:temp].to_f.nonzero? || 1 if temp.zero? tools_present = opts[:tools] && !opts[:tools].empty? tool_temp = (engine[:tool_temp] || 0.1).to_f num_ctx = (engine[:num_ctx] || 32_768).to_i num_predict = (engine[:num_predict] || 4_096).to_i keep_alive = engine[:keep_alive] || '30m' http_body = { model: model, messages: , stream: true, keep_alive: keep_alive, options: { num_ctx: num_ctx, num_predict: num_predict, temperature: tools_present ? tool_temp : temp } } if tools_present http_body[:tools] = opts[:tools] fmt = engine[:format] http_body[:format] = fmt unless fmt.nil? || fmt.to_s.empty? end http_body[:tool_choice] = opts[:tool_choice] if opts[:tool_choice] response = ollama_rest_call( http_method: :post, rest_call: 'api/chat', http_body: http_body, timeout: opts[:timeout], spinner: opts[:spinner] ) raise 'ERROR: Ollama chat_with_tools received empty response from ollama_rest_call' if response.nil? || (response.respond_to?(:empty?) && response.empty?) json_resp = JSON.parse(response, symbolize_names: true) msg = json_resp[:message] || json_resp.dig(:choices, 0, :message) if msg.is_a?(Hash) content = msg[:content].to_s thinking = msg[:thinking].to_s tcalls = Array(msg[:tool_calls]) msg = msg.merge(content: visible_from_thinking(thinking: thinking)) if content.strip.empty? && !thinking.strip.empty? && tcalls.empty? end json_resp[:choices] = [{ message: msg }] if msg && !json_resp.key?(:choices) json_resp[:assistant_message] = msg raise "ERROR: Ollama response missing message/choices: #{json_resp.inspect[0, 400]}" if msg.nil? json_resp rescue StandardError => e raise e end |
.get_models ⇒ Object
- Supported Method Parameters
response = PWN::AI::Ollama.get_models
369 370 371 372 373 374 375 |
# File 'lib/pwn/ai/ollama.rb', line 369 public_class_method def self.get_models models = ollama_rest_call(rest_call: 'api/tags') JSON.parse(models, symbolize_names: true)[:models] rescue StandardError => e raise e end |
.get_plan_usage(opts = {}) ⇒ Object
- Supported Method Parameters
usage = PWN::AI::Ollama.get_plan_usage
Local Ollama has no subscription / plan-usage endpoint. Always unavailable so the PS1 shows the infinity glyph.
382 383 384 385 |
# File 'lib/pwn/ai/ollama.rb', line 382 public_class_method def self.get_plan_usage(opts = {}) _unused = opts { available: false, engine: :ollama, unlimited: true } end |
.help ⇒ Object
Display Usage for this Module
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 |
# File 'lib/pwn/ai/ollama.rb', line 655 public_class_method def self.help puts "USAGE: models = #{self}.get_models usage = #{self}.get_plan_usage response = #{self}.chat( request: 'required - message to Ollama', model: 'optional - model to use for text generation (defaults to PWN::Env[:ai][:ollama][:model])', temp: 'optional - creative response float (defaults to PWN::Env[:ai][:ollama][:temp])', system_role_content: 'optional - context to set up the model behavior for conversation (Default: PWN::Env[:ai][:ollama][:system_role_content])', response_history: 'optional - pass response back in to have a conversation', speak_answer: 'optional speak answer using PWN::Plugins::Voice.text_to_speech (Default: nil)', timeout: 'optional - timeout in seconds (defaults to 900)', spinner: 'optional - display spinner (defaults to false)' ) response = #{self}.chat_with_tools( messages: 'required - messages array', tools: 'optional - OpenAI tools array', model: 'optional - overrides PWN::Env[:ai][:ollama][:model]' ) #{self}.authors " end |