ollama_lam.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. """
  2. agent67.core.ollama_lam — 基于 Ollama 本地模型的 Lam 实现
  3. 无需 API Key,本地运行,完全离线可用。
  4. Lambda 语义不变:
  5. OllamaLam("name", "prompt") ≡ λ_D . F_{ollama,D}
  6. 调用 = β-规约 = ollama.chat 解码
  7. 用法:
  8. brain = OllamaLam("assistant", "你是一个助手", model="qwen2.5:7b")
  9. result = brain("帮我查看文件", ctx)
  10. """
  11. from __future__ import annotations
  12. import json
  13. import time
  14. import urllib.request
  15. import urllib.error
  16. from typing import Any, Callable, Optional
  17. import sys
  18. from pathlib import Path
  19. PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent
  20. sys.path.insert(0, str(PROJECT_ROOT))
  21. from lambdagent.core import Term, Context
  22. OLLAMA_BASE_URL = "http://localhost:11434"
  23. def ollama_available(base_url=OLLAMA_BASE_URL):
  24. """检测 Ollama 服务是否在运行。"""
  25. try:
  26. req = urllib.request.Request("{}/api/tags".format(base_url))
  27. with urllib.request.urlopen(req, timeout=3) as resp:
  28. data = json.loads(resp.read())
  29. return [m["name"] for m in data.get("models", [])]
  30. except Exception:
  31. return []
  32. class OllamaLam(Term):
  33. """
  34. 基于 Ollama 本地模型的 Lambda 抽象。
  35. 通过 Ollama HTTP API (localhost:11434) 调用本地模型,
  36. 支持 Qwen2.5、GLM-4、Llama3 等所有 Ollama 支持的模型。
  37. Lambda: OllamaLam("name", "prompt", model="qwen2.5:7b") = λx. ollama(prompt, x)
  38. """
  39. def __init__(
  40. self,
  41. name: str,
  42. prompt: str,
  43. model: str = "qwen2.5:7b",
  44. temperature: float = 0.3,
  45. max_tokens: int = 4096,
  46. base_url: str = OLLAMA_BASE_URL,
  47. output_parser: Callable[[str], Any] | None = None,
  48. ):
  49. super().__init__(name)
  50. self.prompt = prompt
  51. self.model = model
  52. self.temperature = temperature
  53. self.max_tokens = max_tokens
  54. self.base_url = base_url
  55. self.output_parser = output_parser or (lambda x: x)
  56. def apply(self, input: Any, ctx: Context | None = None) -> Any:
  57. """β-规约: (λ_D x) → ollama.chat(prompt, x)"""
  58. ctx = ctx or Context()
  59. t0 = time.time()
  60. raw = self._call_ollama(str(input))
  61. duration = (time.time() - t0) * 1000
  62. result = self.output_parser(raw)
  63. ctx.log(self._name, self._trace_id, input, result, duration,
  64. "ollama/{}".format(self.model))
  65. return result
  66. def _call_ollama(self, input_text: str) -> str:
  67. """通过 Ollama HTTP API 执行 β-规约。"""
  68. url = "{}/api/chat".format(self.base_url)
  69. body = json.dumps({
  70. "model": self.model,
  71. "messages": [
  72. {"role": "system", "content": self.prompt},
  73. {"role": "user", "content": input_text},
  74. ],
  75. "options": {
  76. "temperature": self.temperature,
  77. "num_predict": self.max_tokens,
  78. },
  79. "stream": False,
  80. }).encode("utf-8")
  81. req = urllib.request.Request(
  82. url, data=body,
  83. headers={"Content-Type": "application/json"},
  84. )
  85. try:
  86. with urllib.request.urlopen(req, timeout=120) as resp:
  87. data = json.loads(resp.read())
  88. message = data.get("message", {})
  89. return message.get("content", "").strip() or "[无输出]"
  90. except urllib.error.URLError as e:
  91. return "[Ollama 错误] 连接失败: {}。请确保 ollama serve 在运行".format(e)
  92. except Exception as e:
  93. return "[Ollama 错误] {}".format(e)
  94. def __rshift__(self, other):
  95. """支持 >> 组合"""
  96. from lambdagent.primitives import Compose
  97. return Compose(self, other)