chatmodeltask 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+
2
+ ---
3
+
4
+ # ⚖️ 4. Add LICENSE (important)
5
+
6
+ Most common:
7
+
8
+ 👉 MIT License
9
+
10
+ Create `LICENSE` file:
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: chatmodeltask
3
+ Version: 0.1.1
4
+ Summary: Smart model routing layer for LangChain + OpenRouter
5
+ Author-email: MO7AMED DEV <programmingi77i@gmail.com>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: redis<6,>=5
10
+ Requires-Dist: httpx<1,>=0.27
11
+ Requires-Dist: langchain<2,>=1.3
12
+ Requires-Dist: langchain-openrouter<1,>=0.2
13
+ Requires-Dist: python-dotenv<2,>=1
14
+ Requires-Dist: thefuzz<1,>=0.22
15
+ Dynamic: license-file
16
+
17
+ # ChatModelTask
18
+
19
+ Smart model routing layer for LangChain + OpenRouter.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install chatmodeltask
25
+ ```
26
+ ```python
27
+ from chatModelTask import ChatModelTask
28
+
29
+ llm = ChatModelTask(task_description="summarization")
30
+ print(llm.invoke("Hello world"))
31
+ ```
32
+
33
+ ## Environment Variables
34
+
35
+ OPENROUTER_API_KEY=...
36
+ REDIS_HOST=localhost
37
+ MODEL_SCORE_ENGINE_BASE_URL=...
38
+ MODEL_SCORE_ENGINE_API_KEY=...
@@ -0,0 +1,22 @@
1
+ # ChatModelTask
2
+
3
+ Smart model routing layer for LangChain + OpenRouter.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install chatmodeltask
9
+ ```
10
+ ```python
11
+ from chatModelTask import ChatModelTask
12
+
13
+ llm = ChatModelTask(task_description="summarization")
14
+ print(llm.invoke("Hello world"))
15
+ ```
16
+
17
+ ## Environment Variables
18
+
19
+ OPENROUTER_API_KEY=...
20
+ REDIS_HOST=localhost
21
+ MODEL_SCORE_ENGINE_BASE_URL=...
22
+ MODEL_SCORE_ENGINE_API_KEY=...
@@ -0,0 +1,9 @@
1
+ from .chatModelTask import ChatModelTask
2
+ from .env import load_env
3
+
4
+ import warnings
5
+ warnings.warn(
6
+ "task_weights will be removed in v1.2",
7
+ DeprecationWarning
8
+ )
9
+
@@ -0,0 +1,152 @@
1
+ from typing import Optional, List, Dict, Any, get_args
2
+ from .model_store import ModelCache
3
+
4
+ from langchain_openrouter import ChatOpenRouter
5
+ from .type import ModelFilter, TASK_TYPE
6
+ from .client import ChatModelTaskClient
7
+ from .model_weight import generateTaskWeights
8
+ from .utils import hash_dict
9
+ from .config import settings
10
+
11
+ #def secret_from_env(key: str) -> Optional[str]:
12
+ # return os.getenv(key)
13
+
14
+ class ChatModelTask(ChatOpenRouter):
15
+
16
+ def __init__(
17
+ self,
18
+ task: Optional[TASK_TYPE] = None,
19
+ task_description: Optional[str] = None,
20
+ task_weights: Optional[dict] = None,
21
+ model: Optional[str] = None,
22
+ filter: Optional[ModelFilter] = None,
23
+ top_k: Optional[int] = 10,
24
+ name: Optional[str] = None,
25
+ base_url: Optional[str] = None,
26
+ api_key: Optional[str] = None,
27
+ openRouter_api_key: Optional[str] = None,
28
+ temperature: Optional[float] = None,
29
+ max_tokens: Optional[int] = None,
30
+ max_completion_tokens: Optional[int] = None,
31
+ timeout: int = 300,
32
+ max_retries: int = 2,
33
+ streaming: bool = False,
34
+ stream_usage: bool = True,
35
+ tags: Optional[List[str]] = None,
36
+ metadata: Optional[Dict[str, Any]] = None,
37
+
38
+ **kwargs: Any
39
+ ):
40
+ """
41
+ Custom ChatModelTask SDK for OpenRouter.
42
+
43
+ Environment Variables Required (if not passed explicitly):
44
+ MODEL_SCORE_ENGINE_BASE_URL: The base URL for the model score engine API.
45
+ MODEL_SCORE_ENGINE_API_KEY: The API secret key for authentication.
46
+ OPENROUTER_API_KEY: the API key for openrouter
47
+
48
+ """
49
+
50
+
51
+ base_url = base_url or settings.MODEL_SCORE_ENGINE_BASE_URL
52
+ api_key = api_key or settings.MODEL_SCORE_ENGINE_API_KEY
53
+ openRouter_api_key = openRouter_api_key or settings.OPENROUTER_API_KEY
54
+
55
+
56
+ if not base_url:
57
+ raise ValueError("MODEL_TASK_BASE_URL is required")
58
+ if not api_key:
59
+ raise ValueError("MODEL_TASK_API_KEY is required")
60
+
61
+ task_description = task if not task_description else task_description
62
+
63
+ payload = {
64
+ "task": task,
65
+ "weights": task_weights if task_weights else None,
66
+ "filter": filter.model_dump() if filter else None,
67
+ "top_k": top_k
68
+ }
69
+
70
+
71
+
72
+ models = None
73
+ if(task_description and not model and not task_weights):
74
+
75
+ model_cache = ModelCache() if task_description else None
76
+ hashFilter = hash_dict(filter.model_dump()) if filter else ''
77
+
78
+ models = model_cache.get_models(
79
+ task_description,
80
+ exp_seconds=3600,
81
+ refresh_func=lambda weights=None:self._refresh_models(weights,task_description,hashFilter,payload,base_url,api_key,timeout),
82
+ hashFilter=hashFilter)
83
+
84
+
85
+ if not models:
86
+ if not model:
87
+ client = ChatModelTaskClient(base_url, api_key, timeout)
88
+ models = client.post_sync("/rank/models", payload)
89
+ else:
90
+ models = [{"id": model}]
91
+
92
+
93
+ super().__init__(
94
+ model=models[0].get('id'),
95
+ temperature=temperature,
96
+ max_tokens=max_tokens,
97
+ max_completion_tokens=max_completion_tokens,
98
+ api_key=openRouter_api_key,
99
+ max_retries=max_retries,
100
+ streaming=streaming,
101
+ tags=tags,
102
+ metadata=metadata,
103
+ stream_usage=stream_usage,
104
+ name=name,
105
+ **kwargs
106
+ )
107
+
108
+
109
+ # FIXED: Use object.__setattr__ to bypass Pydantic validation rules safely
110
+ object.__setattr__(self, 'models', models)
111
+ # Store openRouter_api_key internally so fallbacks can access it if needed
112
+ object.__setattr__(self, '_openrouter_api_key', openRouter_api_key)
113
+
114
+
115
+
116
+ def _refresh_models(self,weights,task_description,hashFilter,payload,base_url, api_key, timeout):
117
+
118
+ isTaskType = task_description in get_args(TASK_TYPE)
119
+
120
+ weights = generateTaskWeights(task_description) if not weights and not isTaskType else weights
121
+ payload['weights'] = weights or {}
122
+
123
+ client = ChatModelTaskClient(base_url, api_key, timeout)
124
+ models = client.post_sync("/rank/models", payload)
125
+
126
+ return {
127
+ "models":[m.get('id') for m in models],
128
+ "weights":weights,
129
+ "hashFilter":hashFilter
130
+ }
131
+
132
+
133
+ def with_fallbacks(self, fallbacks: Optional[List[str]] = None, **kwargs: Any):
134
+ fallbacks = fallbacks if fallbacks else [m.get('id') for i, m in enumerate(self.models) if i > 0]
135
+
136
+ # Extract the saved key to ensure fallback instances can authenticate properly
137
+ api_key = getattr(self, '_openrouter_api_key', None)
138
+
139
+ return super().with_fallbacks(
140
+ [
141
+ ChatOpenRouter(
142
+ model=modelID,
143
+ api_key=api_key,
144
+ **kwargs
145
+ ) for modelID in fallbacks
146
+ ]
147
+ )
148
+
149
+
150
+
151
+
152
+
@@ -0,0 +1,9 @@
1
+ from .env import init_env
2
+
3
+ def main():
4
+ import sys
5
+
6
+ if len(sys.argv) > 1 and sys.argv[1] == "init":
7
+ init_env()
8
+ else:
9
+ print("Usage: chatmodeltask init")
@@ -0,0 +1,58 @@
1
+ import httpx
2
+
3
+
4
+ class ChatModelTaskClient:
5
+
6
+ def __init__(
7
+ self,
8
+ base_url: str,
9
+ api_key: str,
10
+ timeout: int = 300
11
+ ):
12
+ self.base_url = base_url.rstrip("/")
13
+ self.api_key = api_key
14
+ self.timeout = timeout
15
+
16
+ # -------------------------
17
+ # ASYNC version
18
+ # -------------------------
19
+ async def post(self, endpoint: str, json: dict):
20
+
21
+ headers = {
22
+ "Authorization": f"Bearer {self.api_key}"
23
+ }
24
+
25
+ async with httpx.AsyncClient(
26
+ timeout=self.timeout
27
+ ) as client:
28
+
29
+ response = await client.post(
30
+ f"{self.base_url}{endpoint}",
31
+ json=json,
32
+ headers=headers
33
+ )
34
+
35
+ response.raise_for_status()
36
+ return response.json()
37
+
38
+ # -------------------------
39
+ # SYNC version
40
+ # -------------------------
41
+ def post_sync(self, endpoint: str, json: dict):
42
+
43
+ headers = {
44
+ "Authorization": f"Bearer {self.api_key}"
45
+ }
46
+
47
+ with httpx.Client(
48
+ timeout=self.timeout
49
+ ) as client:
50
+
51
+ response = client.post(
52
+ f"{self.base_url}{endpoint}",
53
+ json=json,
54
+ headers=headers
55
+ )
56
+
57
+ response.raise_for_status()
58
+ return response.json()
@@ -0,0 +1,9 @@
1
+ import os
2
+
3
+ class Settings:
4
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
5
+ REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
6
+ MODEL_SCORE_ENGINE_BASE_URL = os.getenv("MODEL_SCORE_ENGINE_BASE_URL")
7
+ MODEL_SCORE_ENGINE_API_KEY = os.getenv("MODEL_SCORE_ENGINE_API_KEY")
8
+
9
+ settings = Settings()
@@ -0,0 +1,24 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ def init_env(path: str = ".env"):
5
+ """
6
+ Create .env file from bundled example
7
+ """
8
+ example_path = Path(__file__).parent / ".env.example"
9
+
10
+ if not example_path.exists():
11
+ raise FileNotFoundError("Missing .env.example in package")
12
+
13
+ if Path(path).exists():
14
+ print(f"{path} already exists")
15
+ return
16
+
17
+ content = example_path.read_text()
18
+ Path(path).write_text(content)
19
+
20
+ print(f"{path} created from template")
21
+
22
+ def load_env():
23
+ from dotenv import load_dotenv
24
+ load_dotenv()
@@ -0,0 +1,131 @@
1
+ import redis
2
+ import json
3
+ import hashlib
4
+ import time
5
+ from typing import List, Optional
6
+ import os
7
+
8
+
9
+ r = redis.Redis(
10
+ host=os.getenv('REDIS_HOST'),
11
+ port=6379,
12
+ decode_responses=True
13
+ )
14
+
15
+
16
+ class ModelCache:
17
+
18
+ # 30 days
19
+ IDLE_EXPIRE_SECONDS = 60 * 60 * 24 * 30
20
+
21
+ def __init__(self):
22
+ self.r = r
23
+
24
+ # -----------------------------
25
+ # key generator
26
+ # -----------------------------
27
+ def make_key(self, task_description: str) -> str:
28
+ hash_value = hashlib.sha256(
29
+ task_description.strip().lower().encode()
30
+ ).hexdigest()
31
+
32
+ return f"model_cache:{hash_value}"
33
+
34
+ # -----------------------------
35
+ # SET
36
+ # -----------------------------
37
+ def set_models(
38
+ self,
39
+ text: str,
40
+ models: List[str],
41
+ weights: dict,
42
+ hashFilter: str = ''
43
+ ):
44
+
45
+ key = self.make_key(text)
46
+
47
+ value = {
48
+ "models": models,
49
+ "weights": weights,
50
+ "updated_at": time.time(),
51
+ "hashFilter": hashFilter
52
+ }
53
+
54
+ # set with TTL
55
+ self.r.set(
56
+ key,
57
+ json.dumps(value),
58
+ ex=self.IDLE_EXPIRE_SECONDS
59
+ )
60
+
61
+ # -----------------------------
62
+ # CHECK IF EXPIRED
63
+ # -----------------------------
64
+ def is_expired(self, data: dict, exp_seconds: int) -> bool:
65
+ return (time.time() - data["updated_at"]) > exp_seconds
66
+
67
+ # -----------------------------
68
+ # GET
69
+ # -----------------------------
70
+ def get_models(
71
+ self,
72
+ text: str,
73
+ exp_seconds: int = 3600,
74
+ refresh_func=None,
75
+ hashFilter: Optional[str] = '',
76
+ ) -> Optional[dict]:
77
+
78
+ key = self.make_key(text)
79
+
80
+ data = self.r.get(key)
81
+
82
+ # -----------------
83
+ # cache miss
84
+ # -----------------
85
+ if not data:
86
+ if refresh_func:
87
+ new_data = refresh_func()
88
+
89
+ self.set_models(
90
+ text,
91
+ new_data["models"],
92
+ new_data["weights"],
93
+ new_data["hashFilter"]
94
+ )
95
+
96
+ return [{"id": m} for m in new_data['models']]
97
+
98
+ return None
99
+
100
+ # refresh idle TTL on access
101
+ self.r.expire(key, self.IDLE_EXPIRE_SECONDS)
102
+
103
+ data = json.loads(data)
104
+
105
+ hashed_filter = data['hashFilter']
106
+
107
+ # -----------------
108
+ # fresh cache
109
+ # -----------------
110
+ if (
111
+ not self.is_expired(data, exp_seconds)
112
+ and hashed_filter == hashFilter
113
+ ):
114
+ return [{"id": m} for m in data['models']]
115
+
116
+ # -----------------
117
+ # expired → refresh
118
+ # -----------------
119
+ if refresh_func:
120
+ new_data = refresh_func(data['weights'])
121
+
122
+ self.set_models(
123
+ text,
124
+ new_data["models"],
125
+ new_data["weights"],
126
+ new_data["hashFilter"]
127
+ )
128
+
129
+ return [{"id": m} for m in new_data['models']]
130
+
131
+ return [{"id": m} for m in data['models']]
@@ -0,0 +1,82 @@
1
+ from chatModelTask.type import CustomWeights
2
+ from langchain_core.prompts import ChatPromptTemplate
3
+
4
+
5
+ weight_data = {
6
+ "popularity": 0.0,
7
+ "reasoning": 0.4,
8
+ "tools": 0.0,
9
+ "reliability": 0.3,
10
+ "cache": 0.0,
11
+ "programming": 0.2,
12
+ "science": 0.0,
13
+ "technology": 0.1,
14
+ "finance": 0.0,
15
+ "marketing": 0.0,
16
+ "translation": 0.0,
17
+ "legal": 0.0,
18
+ "health": 0.0,
19
+ "roleplay": 0.0,
20
+ "academia": 0.0,
21
+ "marketing_seo": 0.0
22
+ }
23
+
24
+
25
+ system_instruction = """
26
+ You are an expert AI Systems Architect specializing in LLM routing and evaluation.
27
+ Your job is to translate a user's natural language request into a precise dictionary of feature weights for a recommendation engine.
28
+
29
+ Available features you can assign weights to:
30
+ - popularity (Overall consumption)
31
+ - reasoning (Complex logic, math, chain-of-thought)
32
+ - tools (Function calling/API execution)
33
+ - reliability (Low error rates in production)
34
+ - cache (Cost optimization/prompt caching)
35
+ - programming (Coding, debugging, script generation)
36
+ - science (Hard sciences, chemistry, physics)
37
+ - technology (IT systems, hardware, generic engineering)
38
+ - finance (Economic analysis, numbers, spreadsheets)
39
+ - marketing (Content creation, ad copywriting)
40
+ - translation (Language accuracy)
41
+ - legal (Law, compliance, text dense evaluation)
42
+ - health (Medicine, wellness data)
43
+ - roleplay (Chat personas, fiction writing)
44
+ - academia (Research papers, thesis styling)
45
+ - marketing/seo (SEO metadata, web positioning)
46
+
47
+ CRITICAL RULES:
48
+ 1. Identify the core intent of the user request and allocate weights only to the most relevant features.
49
+ 2. The SUM of all weights you assign MUST equal exactly 1.0 (or 100%).
50
+ 3. If a feature is completely irrelevant to the user's task, leave it as 0.0.
51
+ 4. Distribute the 1.0 budget intelligently based on the priorities implicit in the user text.
52
+ """
53
+
54
+ def generateTaskWeights(task_description: str) -> CustomWeights:
55
+ from chatModelTask.chatModelTask import ChatModelTask,ModelFilter
56
+
57
+ filter = ModelFilter(max_1m_input_tokens_price=0.1,
58
+ max_1m_output_tokens_price=0.5,
59
+ require_structured=True,
60
+ exclude_free_models=True)
61
+
62
+
63
+ llm = ChatModelTask( task_weights=weight_data,
64
+ filter=filter,
65
+ base_url="http://localhost:8000",
66
+ api_key="123456789",
67
+ temperature=0.0)
68
+
69
+ prompt = ChatPromptTemplate.from_messages([
70
+ ("system", system_instruction),
71
+ ("human", "Analyze this request and generate the weights: '{task_description}'")
72
+ ])
73
+
74
+ # Bind the Pydantic schema to force structured JSON output
75
+ structured_llm = llm.with_structured_output(CustomWeights)
76
+
77
+ # Combine into a runnable chain
78
+ weight_generation_chain = prompt | structured_llm
79
+
80
+ weights = weight_generation_chain.invoke({"task_description":task_description})
81
+
82
+ return weights.model_dump() if weights else None
@@ -0,0 +1,53 @@
1
+ from pydantic import BaseModel,Field
2
+ from typing import Optional,Literal,List
3
+
4
+ MODALITY = Literal['text','image','video','file','audio']
5
+
6
+ class CustomWeights(BaseModel):
7
+ # Global metrics
8
+ popularity: Optional[float] = Field(default=0.0, description="Weight for overall model usage volume.")
9
+ reasoning: Optional[float] = Field(default=0.0, description="Weight for deep thinking, complex logic, and chain-of-thought capabilities.")
10
+ tools: Optional[float] = Field(default=0.0, description="Weight for function calling and external tool usage capabilities.")
11
+ reliability: Optional[float] = Field(default=0.0, description="Weight for low error rates during structured execution.")
12
+ cache: Optional[float] = Field(default=0.0, description="Weight for prompt caching efficiency and cost savings.")
13
+
14
+ # Category metrics
15
+ programming: Optional[float] = Field(default=0.0, description="Weight for software engineering, code generation, and debugging.")
16
+ science: Optional[float] = Field(default=0.0, description="Weight for scientific research, physics, chemistry, etc.")
17
+ technology: Optional[float] = Field(default=0.0, description="Weight for technical, IT, and general engineering topics.")
18
+ finance: Optional[float] = Field(default=0.0, description="Weight for financial analysis, math, spreadsheet logic, and market data.")
19
+ marketing: Optional[float] = Field(default=0.0, description="Weight for copy-writing, creative content generation, and ad hooks.")
20
+ translation: Optional[float] = Field(default=0.0, description="Weight for multi-language translation and localization accuracy.")
21
+ legal: Optional[float] = Field(default=0.0, description="Weight for contract analysis, legal terminology, and compliance.")
22
+ health: Optional[float] = Field(default=0.0, description="Weight for medical, biological, and health-related general information.")
23
+ roleplay: Optional[float] = Field(default=0.0, description="Weight for conversational depth, character persona maintenance, and creative writing.")
24
+ academia: Optional[float] = Field(default=0.0, description="Weight for academic writing, citations, and research papers.")
25
+
26
+ marketing_seo: Optional[float] = Field(default=0.0, alias="marketing/seo", description="Weight specifically for search engine optimization strategy and web ranking copy.")
27
+
28
+ model_config = {
29
+ "populate_by_name": True
30
+ }
31
+
32
+ class ModelFilter(BaseModel):
33
+ max_1m_input_tokens_price: Optional[float] = 1
34
+ max_1m_output_tokens_price: Optional[float] = 2
35
+
36
+ require_tools: Optional[bool] = None
37
+ require_structured: Optional[bool] = None
38
+
39
+ min_context_length: Optional[int] = None
40
+
41
+ exclude_free_models:Optional[bool] = False
42
+
43
+ input_modality:Optional[List[MODALITY]]=['text']
44
+ output_modality:Optional[List[MODALITY]]=['text']
45
+
46
+
47
+
48
+ TASK_TYPE= Literal['programming',
49
+ 'roleplay',
50
+ 'marketing','marketing_seo',
51
+ 'technology','science',
52
+ 'translation','legal','finance',
53
+ 'health','trivia','academia']
@@ -0,0 +1,22 @@
1
+ import hashlib
2
+ import json
3
+
4
+
5
+ def hash_string(text: str) -> str:
6
+
7
+ hash_value = hashlib.sha256(
8
+ text.strip().lower().encode()
9
+ ).hexdigest()
10
+
11
+ return hash_value
12
+
13
+
14
+ def hash_dict(obj: dict) -> str:
15
+
16
+ text = json.dumps(
17
+ obj,
18
+ sort_keys=True,
19
+ separators=(",", ":")
20
+ )
21
+
22
+ return hash_string(text)
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: chatmodeltask
3
+ Version: 0.1.1
4
+ Summary: Smart model routing layer for LangChain + OpenRouter
5
+ Author-email: MO7AMED DEV <programmingi77i@gmail.com>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: redis<6,>=5
10
+ Requires-Dist: httpx<1,>=0.27
11
+ Requires-Dist: langchain<2,>=1.3
12
+ Requires-Dist: langchain-openrouter<1,>=0.2
13
+ Requires-Dist: python-dotenv<2,>=1
14
+ Requires-Dist: thefuzz<1,>=0.22
15
+ Dynamic: license-file
16
+
17
+ # ChatModelTask
18
+
19
+ Smart model routing layer for LangChain + OpenRouter.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install chatmodeltask
25
+ ```
26
+ ```python
27
+ from chatModelTask import ChatModelTask
28
+
29
+ llm = ChatModelTask(task_description="summarization")
30
+ print(llm.invoke("Hello world"))
31
+ ```
32
+
33
+ ## Environment Variables
34
+
35
+ OPENROUTER_API_KEY=...
36
+ REDIS_HOST=localhost
37
+ MODEL_SCORE_ENGINE_BASE_URL=...
38
+ MODEL_SCORE_ENGINE_API_KEY=...
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ chatModelTask/__init__.py
5
+ chatModelTask/chatModelTask.py
6
+ chatModelTask/cli.py
7
+ chatModelTask/client.py
8
+ chatModelTask/config.py
9
+ chatModelTask/env.py
10
+ chatModelTask/model_store.py
11
+ chatModelTask/model_weight.py
12
+ chatModelTask/type.py
13
+ chatModelTask/utils.py
14
+ chatmodeltask.egg-info/PKG-INFO
15
+ chatmodeltask.egg-info/SOURCES.txt
16
+ chatmodeltask.egg-info/dependency_links.txt
17
+ chatmodeltask.egg-info/requires.txt
18
+ chatmodeltask.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ redis<6,>=5
2
+ httpx<1,>=0.27
3
+ langchain<2,>=1.3
4
+ langchain-openrouter<1,>=0.2
5
+ python-dotenv<2,>=1
6
+ thefuzz<1,>=0.22
@@ -0,0 +1 @@
1
+ chatModelTask
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=70", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "chatmodeltask"
7
+ version = "0.1.1"
8
+ description = "Smart model routing layer for LangChain + OpenRouter"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+
12
+ authors = [
13
+ { name="MO7AMED DEV", email="programmingi77i@gmail.com" }
14
+ ]
15
+
16
+ dependencies = [
17
+ "redis>=5,<6",
18
+ "httpx>=0.27,<1",
19
+ "langchain>=1.3,<2",
20
+ "langchain-openrouter>=0.2,<1",
21
+ "python-dotenv>=1,<2",
22
+ "thefuzz>=0.22,<1"
23
+ ]
24
+
25
+ [tool.setuptools]
26
+ include-package-data = true
27
+
28
+ [tool.setuptools.packages.find]
29
+ where = ["."]
30
+ include = ["chatModelTask*"]
31
+
32
+ [tool.setuptools.package-data]
33
+ chatModelTask = ["*.env.example"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+