8000 Model Selection Cookbook (#1814) · jk-codertech/openai-cookbook@c69057d · GitHub
[go: up one dir, main page]

Skip to content

Commit c69057d

Browse files
Model Selection Cookbook (openai#1814)
1 parent d64e85b commit c69057d

13 files changed

+3872
-0
lines changed
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
from __future__ import annotations
2+
import json, time, uuid, logging, re
3+
from dataclasses import dataclass, asdict, field
4+
from pathlib import Path
5+
from typing import Any, Dict, List
6+
from openai import OpenAI
7+
8+
# --- tool back‑ends -------------------------
9+
from tools import chem_lookup, cost_estimator, outcome_db, literature_search, list_available_chemicals
10+
11+
# ---------- tiny infrastructure helpers --------------------------------------
12+
13+
# Holds run-specific parameters provided by user.
14+
@dataclass
15+
class Context:
16+
compound: str
17+
goal: str
18+
budget: float
19+
time_h: int
20+
previous: str
21+
client: OpenAI
22+
run_id: str = field(default_factory=lambda: uuid.uuid4().hex[:8])
23+
24+
def prompt_vars(self):
25+
return {
26+
"compound": self.compound,
27+
"goal": self.goal,
28+
"budget": self.budget,
29+
"time_h": self.time_h,
30+
"previous": self.previous,
31+
}
32+
33+
# -- Function‑calling tool manifest --------------------
34+
35+
def load_tools():
36+
return [
37+
{
38+
"type": "function",
39+
"function": {
40+
"name": "chem_lookup",
41+
8000 "description": "Mock function to look up chemical properties.",
42+
"parameters": {
43+
"type": "object",
44+
"properties": {
45+
"chemical_name": {
46+
"type": "string",
47+
"description": "The name of the chemical to look up."
48+
},
49+
"property": {
50+
"type": "string",
51+
"description": "Optional specific property to retrieve (e.g., 'melting_point'). If None, returns all properties."
52+
}
53+
},
54+
"required": ["chemical_name"]
55+
}
56+
}
57+
},
58+
{
59+
"type": "function",
60+
"function": {
61+
"name": "cost_estimator",
62+
"description": "Mock function to estimate the cost of reagents and procedures.",
63+
"parameters": {
64+
"type": "object",
65+
"properties": {
66+
"reagents": {
67+
"type": "array",
68+
"description": "List of reagents, where each reagent is a dictionary with 'name', 'amount', and 'unit'.",
69+
"items": {
70+
"type": "object",
71+
"properties": {
72+
"name": {"type": "string", "description": "Name of the reagent."},
73+
"amount": {"type": "number", "description": "Amount of the reagent."},
74+
"unit": {"type": "string", "description": "Unit for the amount (e.g., 'g', 'mg', 'kg')."}
75+
},
76+
"required": ["name", "amount", "unit"]
77+
}
78+
},
79+
"equipment": {
80+
"type": "array",
81+
"description": "Optional list of equipment items used.",
82+
"items": {"type": "string"}
83+
},
84+
"duration_hours": {
85+
"type": "number",
86+
"description": "Optional duration of the procedure in hours for labor cost calculation."
87+
}
88+
},
89+
}
90+
}
91+
},
92+
{
93+
"type": "function",
94+
"function": {
95+
"name": "outcome_db",
96+
"description": "Mock function to query the database of past experiment outcomes.",
97+
"parameters": {
98+
"type": "object",
99+
"properties": {
100+
"compound": {
101+
"type": "string",
102+
"description": "The chemical compound name to query past experiments for."
103+
},
104+
"parameter": {
105+
"type": "string",
106+
"description": "Optional specific parameter to filter experiments by (e.g., 'yield', 'temperature')."
107+
},
108+
"limit": {
109+
"type": "integer",
110+
"description": "Maximum number of experiment results to return (default: 5)."
111+
}
112+
},
113+
"required": ["compound"]
114+
}
115+
}
116+
},
117+
{
118+
"type": "function",
119+
"function": {
120+
"name": "literature_search",
121+
"description": "Mock function to search scientific literature for relevant information.",
122+
"parameters": {
123+
"type": "object",
124+
"properties": {
125+
"query": {
126+
"type": "string",
127+
"description": "The search query (keywords) for the literature search."
128+
},
129+
"filter": {
130+
"type": "string",
131+
"description": "Optional filter string, potentially including year (e.g., '2023') or journal name."
132+
},
133+
"limit": {
134+
"type": "integer",
135+
"description": "Maximum number of search results to return (default: 3)."
136+
}
137+
},
138+
"required": ["query"]
139+
}
140+
}
141+
},
142+
{
143+
"type": "function",
144+
"function": {
145+
"name": "list_available_chemicals",
146+
"description": "Provides a list of all chemical names available in the database.",
147+
"parameters": {
148+
"type": "object",
149+
"properties": {},
150+
# No parameters needed for this tool
151+
}
152+
}
153+
}
154+
]
155+
156+
# -- minimal logger -----------------------------------------------------------
157+
158+
def log_json(stage: str, data: Any, ctx: Context):
159+
Path("logs").mkdir(exist_ok=True)
160+
p = Path("logs") / f"{ctx.run_id}.log"
161+
with p.open("a", encoding="utf-8") as f:
162+
f.write(json.dumps({"ts": time.time(), "stage": stage, "data": data}, indent=2) + "\n")
163+
164+
# -- JSON extractor -----------------------------------------------------
165+
166+
def _parse_json(text: str) -> Dict[str, Any]:
167+
try:
168+
return json.loads(text)
169+
except json.JSONDecodeError:
170+
# try to rescue JSON from a ```json ...``` block
171+
m = re.search(r"```(?:json)?\\s*(.*?)```", text, re.S)
172+
if m:
173+
try:
174+
return json.loads(m.group(1))
175+
except json.JSONDecodeError:
176+
pass # fall-through to raw
177+
return {"raw": text} # give caller *something* parsable
178+
179+
180+
# -- tool call handler --------------------------------------------------------
181+
182+
def _dispatch_tool(name: str, args: Dict[str, Any]):
183+
"""Run the local Python implementation of a tool.
184+
If the model supplied bad / missing arguments, return an error JSON instead
185+
of raising – so the conversation can continue."""
186+
try:
187+
return {
188+
"chem_lookup": chem_lookup,
189+
"cost_estimator": cost_estimator,
190+
"outcome_db": outcome_db,
191+
"literature_search": literature_search,
192+
"list_available_chemicals": list_available_chemicals,
193+
}[name](**args)
194+
except TypeError as e:
195+
# log & surface the problem back to the model in a structured way
196+
logging.warning(f"Tool {name} failed: {e}")
197+
return {"tool_error": str(e), "supplied_args": args}
198+
199+
# -- unified OpenAI call w/ recursive tool handling ---------------------------
200+
201+
def call_openai(client: OpenAI, model: str, system: str, user: str, ctx: Context):
202+
messages = [
203+
{"role": "system", "content": system},
204+
{"role": "user", "content": user},
205+
]
206+
while True:
207+
resp = client.chat.completions.create(
208+
model=model,
209+
messages=messages,
210+
tools=load_tools(),
211+
tool_choice="auto",
212+
)
213+
msg = resp.choices[0].message
214+
messages.append(msg.model_dump(exclude_unset=True))
215+
if not msg.tool_calls:
216+
log_json(model, msg.content, ctx)
217+
return _parse_json(msg.content)
218+
# handle first tool call, then loop again
219+
for tc in msg.tool_calls:
220+
result = _dispatch_tool(tc.function.name, json.loads(tc.function.arguments))
221+
messages.append({
222+
"role": "tool", "tool_call_id": tc.id,
223+
"content": json.dumps(result)
224+
})
225+
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)
0